본문으로 건너뛰기

스트림 기본연습

Maven프로젝트로 생성했고 아키타입 메이븐 퀵 스타트로 만들었다.
코드로 바로 보자.

DB없이 진행을 위해 Member클래스를 이렇게 만들었다.

public static메서드로 리스트에 원하는 값을 담아서 반환하도록 했다.


@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Member {

private Integer id;

private String name;

private Integer age;

private String gender;

private String location;

private Integer memberLevel;

public static List<Member> getMembersList() {

return Arrays.asList(
new Member(1, "cavin", 15, "male", "Tokyo", 5),
new Member(2, "james", 21, "male", "Seoul", 5),
new Member(3, "lucy", 33, "female", "Pusan", 5),
new Member(4, "bob", 25, "male", "Tokyo", 4),
new Member(5, "david", 40, "male", "Tokyo", 4),
new Member(6, "susan", 65, "female", "Seoul", 2),
new Member(7, "cherky", 7, "male", "Seoul", 1),
new Member(8, "keeth", 12, "male", "Seoul", 3),
new Member(9, "can", 11, "female", "Seoul", 3),
new Member(10, "mantis", 34, "male", "Tokyo", 1),
new Member(11, "jenkins", 34, "male", "Seoul", 1)
);
}

}

참고로 롬복은 메이븐일 경우 dependencies 안에 박아준다.

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>

gradle일 경우

compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.20'

public class App {
public static void main(String[] args) {

// 스트림을 안쓰고 전통적인 방법으로 할 때
for (Member member : Member.getMembersList()) {
if (member.getGender() == "male") {
System.out.println(member);
}
}

/**
* 스트림을 사용하면 코드가 간결해진다. 람다를 사용하는것도 좋은데 메서드 참조를 사용하면 더욱 간결해진다.
* 스트림은 stream()으로 시작한다. 시작메서드 : stream() -> 중간처리메서드(다중가능) -> 마지막메서드(foreach/collect등)
*
* 중간처리메서드는 여러개일수도 있고 없을 수도 있다.
* 반드시 종료처리메서드를 써야하는것은 아니다. 종료처리메서드 없을 수도 있다. 종료처리메서드 없거나 혹은 단 하나.
*
* 중간처리메서드 에서 많이 사용하는것은 filter, distinct
* map, flatmap(Stream<Stream>같이 스트림의 스트림을 -> 스트림 형으로 변환할 때 사용)
* sorted
* count,max, min,
* skip, limit
* peek
*
* 종료처리메서드 는 foreach / collect
* allMatch / anyMatch / noneMatch
* findFirst / findAny
* reduce
*
* map같은 경우는 List형태의 스트림을 String으로 바꾼다던지. 하는등의 형태를 바꿀때에 많이 쓰인다.(안바꾸고 그대로 List -> List로 하기도 한다)
* peek메서드는 처리부분 메서드체이닝으로 길어질때 중간중간에 박아서 로그로 찍어볼 때 쓴다고 한다. 일단 그런것이 있다는 것은 알고 가자.
*
*/

// stream()으로 시작
// 중간처리인 필터로 필터링 한다. 필터링후 다음 처리로 넘긴다.
// collect Collectors.toList함수로 리스트로 모아져서 리스트를 반환한다.
// 즉, 전체에서 필터를 통과하는 객체만 넘어가고 그것들만 모아서 리스트로 반환.
List<Member> memberList = Member.getMembersList().stream().filter(m -> m.getGender() == "male").collect(Collectors.toList());
System.out.println("####### memberList ######");
memberList.forEach(System.out::println);


// 정렬을 할 때 sorted메서드를 쓰고 그 안에서 비교의 대상이 되는 비교자를 전달해주면 된다.
List<Member> sortedList = Member.getMembersList().stream().sorted(Comparator.comparing(Member::getAge)).collect(Collectors.toList());
System.out.println("####### sortedList ######");
sortedList.forEach(System.out::println);

// 모두다 해당되는지 확인하고 true/false를 반환
boolean allMatch = Member.getMembersList().stream().allMatch(m -> m.getAge() >= 20);
System.out.println("모두 나이가 20이상인가?");
System.out.println(allMatch);

// 어느 하나라도 해당되는지 확인하고 true/false를 반환
boolean anyMatch = Member.getMembersList().stream().anyMatch(m -> m.getAge() >= 60);
System.out.println("나이가 60 이상인 데이터가 하나라도 있는가?");
System.out.println(anyMatch);

// 조건을 만족하는 항목이 하나도 없는지 확인
boolean noneMatch = Member.getMembersList().stream().noneMatch(m -> m.getAge() >= 80);
System.out.println("나이가 80 이상인 데이터가 하나도 없는가?");
System.out.println(noneMatch);


// Stream 에서 제공하는 max를 이용하면 특정필드값이 가장 큰 값을 갖는 데이터를 가져올 수 있다.
Optional<Member> maxMember = Member.getMembersList().stream().max(Comparator.comparing(Member::getAge));
if(maxMember.isPresent()) {
System.out.println("나이가 가장 많은 사람은?");
System.out.println(maxMember.get());
// 이렇게 출력하면 옵셔널로 출력되니. 옵셔널 쓸 때에는 옵셔널 객체에서 .get()으로... ^^;;;
System.out.println(maxMember);
}

// Stream 에서 제공하는 min을 이용하면 위와 반대로 특정 필드값이 가장 작은 값을 갖는 데이터를 가져올 수 있다.
Optional<Member> minMember = Member.getMembersList().stream().min(Comparator.comparing(Member::getAge));
if(maxMember.isPresent()) {
System.out.println("나이가 가장 적은 사람은?");
System.out.println(minMember.get());
// 이렇게 출력하면 옵셔널로 출력되니. 옵셔널 쓸 때에는 옵셔널 객체에서 .get()으로... ^^;;;
System.out.println(minMember);
}

// Stream 에서 제공하는 collect와 Collectors.groupingBy를 이용하면 특정한 필드로 그룹을 만들어서 맵을 만들 수 있다.
Map<String, List<Member>> groupBySex = Member.getMembersList().stream().collect(Collectors.groupingBy(Member::getGender));
System.out.println("성별로 male/female 그루핑");
groupBySex.forEach((sex, membersList) -> {
System.out.println(sex);
membersList.forEach(System.out::println);
});

// 중간연산을 여러개 넣어서(메서드채이닝) 복합적으로 사용하는것도 가능하다.
// 멤버중에 여성 이며, 나이가 가장많은 멤버 의 이름을 반환한다.
Optional<String> oldestWomanMemberName = Member.getMembersList().stream()
.filter(m -> m.getGender() == "female").max(Comparator.comparing(Member::getAge)).map(Member::getName);
System.out.println("멤버중 여자이며 나이가 가장많은 사람의 이름을 출력");
oldestWomanMemberName.ifPresent(System.out::println);

// 나이가 20이상이면서 남자 이면서 서울에 살고 있는 사람을 뽑아보자
List<Member> filteredMember = Member.getMembersList().stream()
.filter(m -> m.getAge() >= 20)
.filter(m -> m.getGender() == "male")
.filter(m -> m.getLocation() == "Seoul")
.collect(Collectors.toList());
System.out.println("나이가 20이상이면서 남자 이면서 서울에 살고 있는 사람");
System.out.println(filteredMember);

// 나이가 10이상 이면서 남자 이면서 서울에 살고있는 사람의 이름을 뽑아보자
List<String> filteredMemberName = Member.getMembersList().stream()
.filter(m -> m.getAge() >= 10)
.filter(m -> m.getGender() == "male")
.filter(m -> m.getLocation() == "Seoul")
.map(Member::getName)
.collect(Collectors.toList());

System.out.println("나이가 10이상 이면서 남자 이면서 서울에 살고있는 사람의 이름");
filteredMember.forEach(System.out::println);

// 서울에 살고 있는 사람들을 나이순으로 정렬해서 리스트로 반환해보자
List<Member> filteredListOfAgeLocation = Member.getMembersList().stream()
.filter(s -> s.getLocation() == "Seoul")
.sorted(Comparator.comparing(Member::getAge))
.collect(Collectors.toList());
System.out.println("서울에 살고 있는 사람들을 나이순으로 정렬해서 리스트로 반환해보자");
filteredListOfAgeLocation.forEach(System.out::println);

// 멤버 레벨이 2보다 높은 남자중에 나이가 제일 어린 사람 이름을 반환해보자
Optional<String> filteredName = Member.getMembersList().stream()
.filter(m -> m.getMemberLevel() > 2)
.filter(m -> m.getGender() == "male")
.min(Comparator.comparing(Member::getAge))
.map(Member::getName);

System.out.println("멤버 레벨이 2보다 높은 남자중에 나이가 제일 어린 사의람의 이름");
filteredName.ifPresent(System.out::println);


// 멤버 레벨별로 그루핑해서 출력해보자
Map<Integer, List<Member>> groupByMemberLevel = Member.getMembersList().stream()
.collect(Collectors.groupingBy(Member::getMemberLevel));
System.out.println("멤버 레벨별로 그루핑");
groupByMemberLevel.forEach((memberLevel, membersList) -> {
System.out.println(memberLevel);
membersList.forEach(System.out::println);
});
}
}

옵셔널로 단일값을 반환 할 때

Optional<String> someMinMember = memberList.stream().  // ..somecode       min() 같이 단일값으로 반환하덩가
Optional<Integer> someMaxMember = memberList.stream(). // ..somecode max() 같이 단일값으로 반환하덩가

옵셔널+단일값 형태 로 반환 할 때에는 min() 이나 max()같이 단일로 하나만 택하게되는 중간연산을 통해서 그것을 최종적으로 map을 이용해서 반환해야한다.
뭐.. 당연한 것이지만.. -_-;;
그렇게 안하면 반환타입 형 불일치로 안된다.