자바 1.0에서 java.util.Date 클래스로 날짜와 시간 관련 기능을 제공하였으나

1900년 기준으로 하는 오프셋, 0으로 시작하는 달 인덱스 등 애매한 설계로

유용성이 떨어진다는 이야기가 많았고, 이를 보완할 수 있게 자바 1.1에서 Calandar 클래스가

추가되었으나 Daeformat 같은 일부 기능 들은 Date 클래스에서만 동작한다던 지

DateFormat이 스레드에 안전하지 않다 던지 여러 가지 문제점이 존재하였습니다.

자바 9에서 이를 보완 가능한 java.util.time 패키지가 추가되었고 이에 대해 알아보겠습니다.

 

LocalDate

시간을 제외한 날짜를 표현하는 불변 객체입니다.

어떤 시간대 정보도 포함하지 않습니다.

정적 팩토리 메서드 of로 LocalDate 인스턴스 생성 가능하고

연도, 달, 요일 등을 반환하는 메서드 제공합니다.

예제로 사용 예시를 확인해보겠습니다.

메서드 이름도 굉장히 직관적이고 이전에 비해 사용이 매우 쉬운 편입니다.

import java.time.*;

public class Main {
	public static void main(String[] args) {
		LocalDate date = LocalDate.of(2022, 10, 22);
		LocalDate parseDate = LocalDate.parse("2022-10-22");
		LocalDate today = LocalDate.now();
		int year = date.getYear();
		Month month = date.getMonth();
		DayOfWeek dow = date.getDayOfWeek();
		int len = date.lengthOfMonth();
		boolean leap = date.isLeapYear();
        
        System.out.println(date);
		System.out.println(parseDate);
		System.out.println(today);
		System.out.println(year);
		System.out.println(month);
		System.out.println(dow);
		System.out.println(len);
		System.out.println(leap);
    }
}

출력 값

2022-10-22
2022-10-22
2022-11-28
2022
OCTOBER
SATURDAY
31
false

 

LocalTime

시간 표현이 가능한 객체입니다.

LocalDate 객체와 사용 방법이 유사합니다.

다음 예제를 통해 사용 예시를 확인해보겠습니다.

import java.time.*;

public class Main {
	public static void main(String[] args) {
		LocalTime time = LocalTime.of(15, 56, 59);
        // 포맷이 틀릴 경우 DateTimeParseException 발생.
		LocalTime parseTime = LocalTime.parse("13:56:59"); 
        
		
		int hour = time.getHour();
		int minute = time.getMinute();
		int sec = time.getSecond();
		
		System.out.println(time);
		System.out.println(parseTime);
		
		System.out.println(hour);
		System.out.println(minute);
		System.out.println(sec);	
    }
}

출력 값

15:56:59
13:56:59
15
56
59

 

LocalDateTime

LocalDate와 LocalTime을 갖는 복합 클래스입니다.

날짜와 시간을 모두 표현 가능합니다.

다음 예제를 통해 사용 예시를 확인해보겠습니다.

 

import java.time.*;

public class Main {
	public static void main(String[] args) {
		LocalDateTime dt1 = LocalDateTime.of(2022, Month.DECEMBER, 22, 13, 56, 59);
		LocalDateTime dt2 = LocalDateTime.of(date, time);
		LocalDateTime dt3 = date.atTime(13, 45, 20);
		LocalDateTime dt4 = date.atTime(time);
		LocalDateTime dt5 = time.atDate(date);
		
		LocalDate ldate1 = dt1.toLocalDate();
		LocalTime time1 = dt1.toLocalTime();
		
		System.out.println(dt1);
		System.out.println(dt2);
		System.out.println(dt3);
		System.out.println(dt4);
		System.out.println(dt5);
		System.out.println(ldate1);
		System.out.println(time1);
    }
}

출력 값

2022-12-22T13:56:59
2022-10-22T15:56:59
2022-10-22T13:45:20
2022-10-22T15:56:59
2022-10-22T15:56:59
2022-12-22
13:56:59

 

 

'Java > Time' 카테고리의 다른 글

ZoneId, ZoneDateTime, ZoneOffset  (0) 2022.11.28
TemporalAdjusters와 DateTimeFormatter  (0) 2022.11.28
Instant, Duration, Period  (0) 2022.11.28

바로 공백 없이 일을 시작하려던 차에 시기적절하게 2년간 묶여 있던 항공권 티켓을 환불받았습니다.

고민 중에 잠시 휴식 시간을 가지는 겸 못 갔던 신혼여행을 갔다 오는 게 나을 것 같아 

행동에 옮겼는데 귀국 후에 며칠이 지나고 단순 감기몸살인 줄 알았는데 코로나 확진을 받고 요양 중에 있습니다.

해외에서 상시 마스크 착용하고 야외 활동은 사람이 밀집되지 않아서 크게 걱정은 안 했는데

아마도 식당에서 감염이 된 것 같네요.

많이 약해졌다고 하지만 코로나는 코로나인 듯합니다.

이제 어느 정도 컨디션이 올라와서 미뤄놨던 공부&정리 작업을 다시 시작합니다.

 

'생각정리' 카테고리의 다른 글

블로그 이전  (0) 2023.11.05
코딩 테스트 인터뷰 리뷰  (0) 2023.01.11
이직  (0) 2022.07.30
인앱 결제 진행 중 생각정리  (0) 2022.05.26
코딩테스트  (0) 2022.03.30

자바의 모든 객체는 null 일 수 있고 null을 참조할 경우 NullPointerException을 만나게 됩니다.

NullPointerException가 무엇인지 자바 기초 문법을 익힌 사람이라면 대부분 아는 내용이므로

이 글에서 자세하게 언급하지는 않겠습니다.

현업에서 종종 레거시 프로젝트에서 한 번쯤은 확인 가능한 코드가 있습니다.

어떤 조건들에 대해 무한히 체크하는 패턴을 볼 수 있습니다.

특히 비즈니스 로직이 복잡한 곳이 그런데요.

아래와 같이 a, b, c 객체의 null 체크 조건식과 그에 따른 비즈니스 로직이 추가되는 sudo 코드입니다.

체크해야 할 조건이 늘어날수록 코드는 복잡해지고 가독성도 떨어지게 됩니다.

이러한 반복되는 패턴 코드를 깊은 의심(deep doubt) 이라고도 합니다.

if (a != null) { // 객체 a의 null 체크 
	B b = a.getXxx();
    ...
    if ( b != null) { // 객체 b의 null 체크 
    	C c = b.getXxx();
        ...
        if ( c != null) { // 객체 c의 null 체크 
        	D d = c.getXxx();
            ...
        }
    }
}

 

자바 8 이후로 추가된 Optional 클래스를 통해 NullPointerException 등 null 참조로 인해 발생하는 문제들을

처리하는 코드를 가독성 있게 작성할 수 있습니다.

java.util.Optional<T> 클래스는 선택형 값을 캡슐화하는 클래스입니다.

예제를 통해서 기능들을 확인해보겠습니다.

 

Phone, Person, Insurance, Main 클래스를 각각 생성하겠습니다.

처음 예제와 같이 null 참조에 대해 조건문으로 처리한 예제이고

Insurance → Phone Person  중첩되어 있는 구조입니다.

public class Insurance {
	private String name;
	public String getName() { return name; }
}
public class Phone {
	private Insurance insurance;
	public Insurance getInsurance() { 
		return insurance;
	};
}
public class Person {
	private Phone phone;
	
	public Phone getPhone() {
		return phone;
	}
}
public class Main {
	public static void main(String[] args) {
		Person p = new Person();
		System.out.println(getPhoneInsuranceName(p));
	}
	
	public static String getPhoneInsuranceName(Person person) {
		if (person != null) {
			Phone phone = person.getPhone();
			if(phone != null) {
				Insurance insurance = phone.getInsurance();
				if(insurance != null) {
					return insurance.getName();
				}
			}
		}
		return "None";
	}
}

이전 문법으로 이렇게 처리는 가능합니다

여기서 조금 더 복잡해져서 if 문을 누락한다면? 런타임에서 null 참조가 있는 속성에 접근 전까지는 알 수가 없습니다.

Optional 클래스를 이용해 개선해보겠습니다.

import java.util.Optional;

public class Person {
	private Optional<Phone> phone;
	
	public Optional<Phone> getPhone() {
		return phone;
	}
}
import java.util.Optional;

public class Phone {

	private Optional<Insurance> insurance;

	public Optional<Insurance>  getInsurance() { 
		return insurance;
	}
}
public class Insurance {
	private String name;
	public String getName() { return name; }
}
import java.util.Optional;

public class Main {
	public static void main(String[] args) {
		Person person = new Person();
		Optional<Person> optPerson = Optional.of(person);
		System.out.println(getPhoneInsuranceName(optPerson));
	}
	
	public static String getPhoneInsuranceName(Optional<Person> person) {
		String name = person.flatMap(Person::getPhone)
						.flatMap(Phone::getInsurance)
						.map(Insurance::getName)
						.orElse("None");
		return name;
	}
}

Main의 테스트 코드를 실행하면 NullPointerException이 발생합니다.

기존 처리방식의 코드와 차이점은 Person 속성에 접근하여 null 참조하기 전에 NullPointerException이 발생하게 됩니다.

도메인에서 사용 주의점은 직렬화에서 문제가 발생하는데 이것은 자바 설계 상 Optional의 용도가

선택형 반환 값을 지원하지 않게 설계됐기 때문입니다. 필드 형식으로 사용하지 않는 것을 가정하므로 Serializable 인터페이스를 구현하지 않기 때문입니다.

 

Optional은 예제에서 사용한 of 메서드 포함해서 다음과 같은 메서드를 제공합니다.

 

empty 메서드

빈 Optional 객체를 얻을 수 있습니다.

Optional<Person> optPerson = Optional.empty();

 

of 메서드

null 아닌 값을 포함하는 Optional을 만들 수 있습니다.

person이 null 이면 즉시 NullPointerException 발생.

Person person = new Person();
Optional<Person> optPerson = Optional.of(person);

 

ofNullable 메서드

null 값을 저장할 수 있는 Optional을 만들 수 있습니다.

person이 null이면 빈 Optional 객체를 반환합니다.

Person person = new Person();
Optional<Person> optPerson = Optional.ofNullable(person);

 

get 메서드

Optional 랩핑 된 값이 있으면 반환하고 없으면 NoSuchElementException을 발생시킵니다.

따라서 반드시 Optional에 값이 있다고 가정할 수 있는 상황에서만 사용하는 것이 바람직합니다.

Person person = new Person();
Person tmp = Optional.get(person);

 

orElse 메서드

Optional 랩핑된 값이 있는 경우 해당 값을 반환하고 랩핑된 값이 없으면 인수(기본값)를 제공.

첫 번째, 두 번째 줄 주석을 각각 해제/주석 처리하면서 테스트해보면 어느 때나 randomName 메서드를 호출하는 것을

알 수 있습니다.

import java.util.Optional;
import java.util.Random;

public class Main {
	public static void main(String[] args) {
		//Optional<String> optStr = Optional.empty();
		Optional<String> optStr = Optional.of("ABC");
		System.out.println(optStr.orElse(randomName()));
	}
	
	public static String randomName() {
		System.out.println("randomName");
		return "T" + new Random().nextInt(); 
	}
 }

 

orElseGet 메서드

Optional 랩핑 된 값이 있는 경우 해당 값을 반환하고 랩핑된 값이 없으면 Supplier를 실행하고 호출 결과를 반환.

첫 번째, 두 번째 줄 주석을 각각 해제/주석 처리하면서 테스트해보면 null 일 경우에만 randomName() 메서드가 호출되는 것을 확인 가능합니다.

import java.util.Optional;
import java.util.Random;

public class Main {
	public static void main(String[] args) {
		Optional<String> optStr = Optional.empty();
		//Optional<String> optStr = Optional.of("ABC");
		System.out.println(optStr.orElseGet(() -> randomName()));
		//System.out.println(getPhoneInsuranceName(optPerson));
	}
	
	public static String randomName() {
		System.out.println("randomName");
		return "T" + new Random().nextInt(); 
	}
}

 

orElseThrow

Optional 랩핑 값이 존재하면 값을 반환하고, 값이 없으면 Supllier에서 생성한 예외 발생.

import java.util.Optional;
import java.util.Random;

public class Main {
	public static void main(String[] args) {
		Optional<String> optStr = Optional.empty();
		//Optional<String> optStr = Optional.of("ABC");
		try {
			System.out.println(optStr.orElseThrow(() -> new Exception("orElseThrow")));
		} catch(Exception e) {
			e.printStackTrace();
		}
		//System.out.println(getPhoneInsuranceName(optPerson));
	}
}

첫 번째, 두 번째 줄 주석을 각각 해제/주석 처리하면서 테스트해보면 null 일 경우에만 예외 발생.

예외 종류는 선택 가능합니다.

 

 

ifPresent

값이 존재하면 지정된 Consumer를 실행. 

값이 없으면 아무 수행하지 않음.

import java.util.Optional;
import java.util.Random;

public class Main {
	public static void main(String[] args) {
		Optional<String> optStr = Optional.empty();
		//Optional<String> optStr = Optional.of("ABC");
		optStr.ifPresent(t -> randomName());
	}
	
	public static String randomName() {
		System.out.println("randomName");
		return "T" + new Random().nextInt(); 
	}
}

 

ifPresentOrElse

값이 존재하면 지정된 Consumer를 실행.

null 이 빈 값일 경우 Runnable을 지정할 수 있다.

import java.util.Optional;
import java.util.Random;

public class Main {
	public static void main(String[] args) {
		//Optional<String> optStr = Optional.empty();
		Optional<String> optStr = Optional.of("ABC");
		optStr.ifPresentOrElse(t-> System.out.println(t), () -> System.out.println("empty"));
	}
}

 

isPresent

값이 존재하면 true를 반환하고, 값이 없으면 false를 반환.

public class Main {
	public static void main(String[] args) {
		//Optional<String> optStr = Optional.empty();
		Optional<String> optStr = Optional.of("ABC");
		if(optStr.isPresent()) {
			System.out.println("exist");
		} else {
			System.out.println("empty");
		}
	}
}

 

이외에 Stream에서 제공했던 것처럼 map, flatmap도 제공된다.

그리고 기본형 특화 클래스인 OptionalInt, OptionalLong, OptionalDouble 있으나 

map, flatmap, filter 등의 기능들을 지원하지 않는다고 합니다.

 

마치며

Optional이 많은 null 참조 관련 처리를 다 해주는 만능 해결사 노릇을 할 것이라 기대했지만

초기 설계부터가 만능 처리보다는 명시적으로 이것이 선택 값인지 아닌지를 나타내는 

역할에 초점을 맞춘 것으로 생각된다. (도메인 직렬화 문제, get의 경우 NoSuchException 처리 등 )

저 같은 경우 웹 Rest API 개발 시에 요청 변수나 반환 값에 현업에서 적용하여 사용해봤습니다.

적절하게 반환 값 같은 곳에 사용하면 좋은 효과를 거둘 수 있을 것 같습니다.

 

이번 글은 Collector의 분할에 대해서 정리해보겠습니다.

분할 함수는 맵의 key로 Boolean을 반환합니다.

참 아니면 거짓 값을 갖는 두 그룹으로 분류돼 맵이 결과로 반환되게 됩니다.

예제를 통해 확인해보겠습니다.

먼저 예제에 쓰일 games 객체 리스트를 생성합니다.

List<Game> games = Arrays.asList(
		 new Game("CALL 1", 20000, "Shooting")
		,new Game("DIA 3", 30000, "RPG")
		, new Game("DIA 2", 40000, "RPG")
		, new Game("DIA 1", 50000, "RPG")
);

 

partitioningBy

games 객체 리스트에서 RPG 라는 타입으로 분할하여 분류하는 예제입니다. 

Boolean 타입의 각각 false, true의 key 값이 생성되고 partitioningBy에 인수로 넣은 프레디케이트 값에 따라 분류됩니다. 

Map<Boolean, List<Game>> partitionedGame = games.stream()
.collect(partitioningBy(game -> game.getType().equals("RPG")));

partitionedGame.forEach((k,v) -> {
	System.out.println("<"+k+">");
	v.forEach(game -> System.out.println(game.getName()));
	System.out.println();
});

결과값

RPG 타입과 Shooting 타입으로 분류됩니다.

<false>
CALL 1

<true>
DIA 3
DIA 2
DIA 1

 

다음 예제는 앞에서 정리한 groupingBy과 partitioningBy 같이 활용한 예제입니다.

Map<Boolean, Map<String, List<Game>>> rpgGameByType = games.stream()
	.collect(partitioningBy(game -> game.getType().equals("RPG")
	, groupingBy(Game::getType)));
		
rpgGameByType.forEach((k,v) -> {
	System.out.println(k + " >");
	v.forEach((key, value) -> {
		System.out.println(key + " >");
		value.forEach(game -> System.out.println(game.getName()));
	});
	System.out.println();
});

결과값

false >
Shooting >
CALL 1

true >
RPG >
DIA 3
DIA 2
DIA 1

 

다음은 partitioningBy을 사용하여 소수를 분류하는 예제를 확인해보겠습니다.

먼저 소수를 판단하는 리턴하는 정적 메서드를 작성하겠습니다.

앞에서 배운 rangeClosed 메서드로 범위를 정하고 noneMatch 메서드를 사용하여 어떤 값들을 

찾을 것인지 조건을 정합니다.

public static boolean isPrime(int num) {
	int root = (int) Math.sqrt((double) num);
	return IntStream.rangeClosed(2, root).noneMatch(i -> num % i == 0);
}

앞에서 만든 isPrime 메서드를 활용하여 소수와 소수가 아닌 수로 분류하는 스트림을 작성합니다.

public static Map<Boolean, List<Integer>> partitionPrimes(int num) {
	return IntStream.rangeClosed(2, num).boxed()
				.collect(partitioningBy(x -> isPrime(x)));
}

partitionPrimes 메서드를 사용하여 분류하는 테스트를 해봅니다.

1~10까지 소수를 분류하여 출력합니다.

System.out.println(partitionPrimes(10));

결과값

{false=[4, 6, 8, 9, 10], true=[2, 3, 5, 7]}

 

마치며

스트림과 컬렉터를 쭉 살펴보면서 정리하다 보니 기존 DB 레벨에서 사용하던 SQL 처리 기능들을 

많이 Java 코드로 처리할 수 있게 많이 고민을 한 것 같습니다.

이전 글에 groupingBy, maxBy, counting는 SQL에 groupBy, max, count와 매칭이 됩니다.

현업에 있으면서 여러 회사를 거치면서 DB 프로시저로만 모든 비즈니스 로직 구현한 곳도 봤고

프로시저 없이 SQL과 Java를 적절히 섞어서 비즈니스 로직이 구현된 곳을 가장 많이 본듯합니다.

점점 JPA가 자리 잡으면서 예전처럼 Mybatis 프레임워크 위에서 SQL를 직접 구현하는 경우는 점점 줄고 있는데요.

물론 case by case로 적절히 기술을 적용하고 사용해야겠지만 이제는 DB 프로시저나 SQL을 직접 비즈니스 로직

구현하는 경우는 거의 없어지지 않을까 싶습니다.

물론 아직도 개발자가 거의 없거나 개발팀이 없는 경우 최소한에 인력으로 운영하면서 리팩터링에 대한 시간을

아까워하는 곳이 많이 있기 때문에 어느 정도 수요는 유지될 것으로 생각됩니다.

'Java > Stream' 카테고리의 다른 글

Collector 그룹화  (0) 2022.11.01
Collector 요약  (0) 2022.10.28
스트림 생성  (0) 2022.10.27
기본형 특화 스트림  (0) 2022.10.27
리듀싱  (0) 2022.10.26

이번 글에서는 Collector의 그룹화에 대해서 알아보겠습니다.

먼저 그동안 사용했던 games 객체 리스트를 조금 수정해보겠습니다.

 

type 속성을 추가하고, getter/setter 메서드를 추가했습니다.

그리고 생성자는 기존 것 외에 type을 추가하여 3가지 매개변수를 받는 생성자를 추가했습니다.,

package demo;

public class Game {
	private String name;
	private int price;
	private String type;
	
	public Game(String name, int price) {
		this.name = name;
		this.price = price;
	}
	
	public Game(String name, int price, String type) {
		this.name = name;
		this.price = price;
		this.type = type;
	}
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}
}

 

groupingBy

데이터를 하나 이상의 특성으로 분류하여 그룹화는 연산을 지원합니다.

아라 예제는 groupingBy를 활용하여 game 타입 별 그룹 연산을 하고 출력하여 결과를 확인해봅니다.

Map<String, List<Game>> gamesByType = games.stream().collect(groupingBy(Game::getType));
gamesByType.get("RPG").stream().forEach(game -> System.out.println(game.getName()));
gamesByType.get("Shooting").stream().forEach(game -> System.out.println(game.getName()));

결과값

DIA 3
DIA 2
DIA 1
CALL 1

 

인수로 메서드 참조 대신 람다 표현식으로 구현도 가능합니다. 

결과 값은 동일합니다.

Map<String, List<Game>> rpgGame = games.stream().collect(groupingBy(game -> {
	if(game.getType().equals("RPG")) return "RPG"; else return "Shooting";
}));

rpgGame.get("RPG").stream().forEach(game -> System.out.println(game.getName()));
gamesByType.get("Shooting").stream().forEach(game -> System.out.println(game.getName()));

 

그룹화 이후 연산이 하고 싶다면 filtering 등을 이용할 수 있지만 groupingBy 전에 사용하게 되면

특정 결과만 도출하고 반대되는 그룹 값들은 없어지게 된다.

예를 들면 RPG를 filtering 하고 groupingBy 하게 되면 RPG 결과값만 남고 Shooting 값은 사라지게 됩니다.

다음 예제와 같이 groupingBy에 인수로 filtering을 사용하면 비어있는 그룹의 값으로 결과 값이 반환됩니다.

40,000 초과하는 가격을 가진 게임 목록에 대해서 filtering 하고 그룹화합니다.

Map<String, List<Game>> priceGameByType = games.stream()
	collect(groupingBy(Game::getType, filtering(game -> game.getPrice() > 40000, toList())));
    
priceGameByType.forEach((k,v) -> {
	System.out.print(k + " : ");
	v.forEach(game -> System.out.println(game.getName()));
	System.out.println();
});

결과값

Shooting 목록은 해당되는 요소가 없지만 그렇다고 해당 그룹 자체 없이 결과 값이 반환되지는 않는 것이 포인트입니다.

Shooting : 
RPG : DIA 1

 

filtering 외에 mapping 등 다양한 응용이 가능합니다.

다음 예제는 앞에서 정리했던 joining 메서드를 사용하여 각 요소들을  "," 구분자로 합쳐서 결과 값을 그룹화하여

출력해보겠습니다.

Map<String, String> gameNameByType = games.stream()
	.collect(groupingBy(Game::getType, mapping(Game::getName, joining(","))));
    
gameNameByType.forEach((k,v) -> {
	System.out.print(k + " : " + v);
	System.out.println();
});

결과값

Shooting : CALL 1
RPG : DIA 3,DIA 2,DIA 1

 

다음 예제는 counting 메서드를 사용하여 그룹화된 값들에 대해 합계를 구하여 결과를 반환합니다.

Map<String, Long> typesCount = games.stream().collect(groupingBy(Game::getType, counting()));

typesCount.forEach((k,v) -> {
	System.out.print(k + " : " + v);
	System.out.println();
});

결과값

Shooting : 1
RPG : 3

 

다음은 2번 groupingBy를 사용하여 여러 번 그룹화를 적용해보겠습니다.

Map<String, Map<String, List<Game>>> gameMultiGrouping = games.stream().collect(groupingBy(Game::getType,
		groupingBy(game -> {
			if(game.getPrice() > 20000) {
				return "RPG";
			} else {
				return "Shooting";
			}
		})
	)
);
		
gameMultiGrouping.forEach((k,v) -> {
	System.out.println("<"+k+">");
	v.get(k).forEach(game -> System.out.println(game.getName()));
	System.out.println();
});

결과값

<Shooting>
CALL 1

<RPG>
DIA 3
DIA 2
DIA 1

 

collectingAndThen

다음 예제는 maxBy를 사용하여  각각 그룹에 가장 비싼 게임을 결과값을 반환하겠습니다.

여기서 결과 값은 collectingAndThen을 사용하여 다른 형식으로 변환합니다.

참고할 점은 리듀싱 컬렉터의 경우에는 Optional.empty()를 반환하지 않으므로 안전합니다.

Map<String, Game> mostPriceBytype = games.stream().collect(groupingBy(Game::getType,
collectingAndThen(maxBy(Comparator.comparingInt(Game::getPrice)), Optional::get)));

결과값

Shooting : CALL 1
RPG : DIA 1

 

'Java > Stream' 카테고리의 다른 글

Collector 분할  (0) 2022.11.08
Collector 요약  (0) 2022.10.28
스트림 생성  (0) 2022.10.27
기본형 특화 스트림  (0) 2022.10.27
리듀싱  (0) 2022.10.26

Collector 인터페이스 구현은 스트림의 요소를 어떤 식으로 도출할지 결정.

예를 들면 Collector 인터페이스의 구현으로 toLost는 각 요소들을 리스트로 만들어줍니다.

미리 정의된 컬렉터의 기능과 커스텀해서 만드는 기능에 대해서 정리해보려고 합니다.

크게 Collector 는 요약, 그룹화, 분할로 나눌 수 있습니다.

이번 글에서는 요약에 대해서 정리해보겠습니다.

 

먼저 예제에서 사용할 Game 리스트를 생성해보겠습니다.

List<Game> games = Arrays.asList(
		new Game("CALL 1", 20000)
		, new Game("DIA 3", 30000)
		, new Game("DIA 2", 40000)
		, new Game("DIA 1", 50000)
);

 

counting

아래 예제는 리스트 모든 요소들을 카운팅 하는 예제입니다.

long countGames = games.stream().collect(Collectors.counting());

다음과 같이 Colletors 클래스를 import 하면 아래와 같이 더욱더 간단하게 작성이 가능합니다.

이제부터 살펴볼 예제는 Collectors 클래스를 import 한 상태로 진행하겠습니다.

import static java.util.stream.Collectors.*;
long countGames = games.stream().collect(counting());

 

maxBy와 minBy

최대값 최솟값을 구하는 예제를 살펴보겠습니다.

Comparator<Game> gamesComparator = Comparator.comparingInt(Game::getPrice);
Optional<Game> maxPriceGame = games.stream()
				.collect(maxBy(gamesComparator)); 
                
Optional<Game> minPriceGame = games.stream()
				.collect(minBy(gamesComparator)); 
		
System.out.println(maxPriceGame.get().getPrice());
System.out.println(minPriceGame.get().getPrice());

결과값

50000
20000

 

summingInt

객체를 int로 맵핑하는 함수를 인수로 받아 객체를 int 로 맵핑하여 컬렉터를 반환하는 메서드.

int totalPriceGame = games.stream().collect(summingInt(Game::getPrice));
System.out.println(totalPriceGame);

결과값

140000

 

averagingInt

평균값을 계산하는 메서드.

double avgPriceGame = games.stream().collect(averagingInt(Game::getPrice));
System.out.println(avgPriceGame);

결과값

35000.0

 

summarizingInt

요소를 카운팅 한 숫자, 합계, 평균, 최댓값, 최솟값을 연산하는 메서드.

IntSummaryStatistics priceGameStat = games.stream().collect(summarizingInt(Game::getPrice));
System.out.println(priceGameStat);

결과값

IntSummaryStatistics{count=4, sum=140000, min=20000, average=35000.000000, max=50000}

 

joining

각 객체의 toString 메서드를 호출해서 추출한 문자열들을 하나의 문자열로 합쳐서 반환.

String gameName = games.stream()
		.map(game -> game.getName().split(" ")[0]) // 문자열을 쪼개서 첫번째 문자열을 생성.
		.collect(joining(",")); // 각 첫번째 문자열을 합친다. 구분자는 ","
        
System.out.println(gameName);

결과값

CALL,DIA,DIA,DIA

 

reducing

리듀싱 구현 기능을 제공하는 메서드.

아래는 price 합을 구하는 예제를 reducing 메서드를 이용해 구현.

int totalPriceGame = games.stream().collect(reducing(0, Game::getPrice, (i, j) -> i + j));
System.out.println(totalPriceGame);

첫 번째 인수는 시작 값 또는 인수가 없을 때 반환 값.

두 번째 인수는 price 반환 함수

세 번째 인수는 두 개의 항목을 하나로 합하는 BinaryOperator 이다.

 

결과값

140000

 

다음은 price 최대 값을 구하는 예제.

Optional<Game> maxGamePrice = games.stream().collect(Collectors.reducing((i, j) -> i.getPrice() > j.getPrice() ? i : j));
System.out.println(maxGamePrice.get().getPrice());

결과값

50000

 

 

'Java > Stream' 카테고리의 다른 글

Collector 분할  (0) 2022.11.08
Collector 그룹화  (0) 2022.11.01
스트림 생성  (0) 2022.10.27
기본형 특화 스트림  (0) 2022.10.27
리듀싱  (0) 2022.10.26

+ Recent posts