해당 게시글은 실무 자바 개발을 위한 OOP와 핵심 디자인 패턴 강의를 참고해서 작성한 게시글임을 밝힙니다.
map 은 매핑 즉, 스트림 요소를 다른 요소로 변환하는 중간 처리 기능에 초점이 맞춰져 있다. 그렇기 때문에 변환한 새로운 스트림을 리턴 한다.
forEach 는 루핑 즉, 스트림에서 요소를 하나씩 반복해서 가져와 처리하는 것에 초점이 맞춰져 있다. 그렇기 때문에 새로운 스트림을 리턴하지 않는다. 또한 최종 처리 메소드 이다.
TIP.
map 이 새로운 스트림을 리턴해도, 원본 스트림 원소가 레퍼런스 타입이라면 원본 스트림의 요소들과 동일한 객체를 참조 한다.
원하는 원소를 찾으면 break; 하는 코드는 filter() 로 대체 가능하다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
Optional<String> result = names.stream().filter(name -> name.startsWith("C")).findAny();
result.ifPresent(System.out::println); // 결과로 "Charlie"가 출력될 수도 있지만, 항상 그런 것은 아님.
해당 코드는 매칭되는 원소를 찾으면 바로 멈춘다. = 전체 순회 X
하지만 극악의 확률로 리스트 순서 상 가장 처음으로 매칭되는 값이 출력 되지 않을 수 있다.
이유는? 밑에!
findAny()의 api 명세서 이다.
Optional findAny()
스트림의 어떤 요소를 나타내는 Optional을 반환하거나, 스트림이 비어 있을 경우 빈 Optional을 반환합니다.
이는 단축(short-circuiting) 종료 연산입니다.
이 연산의 동작은 명시적으로 비결정적(nondeterministic)입니다; 스트림의 어떠한 요소라도 선택할 자유가 있습니다. 이는 병렬 연산에서 최대 성능을 허용하기 위함입니다; 단점은 동일한 소스에서 여러 번 호출할 때 동일한 결과를 반환하지 않을 수 있다는 것입니다. (안정적인 결과가 필요한 경우 findFirst()를 사용하세요.)반환 값:
이 스트림의 어떤 요소를 나타내는 Optional이거나, 스트림이 비어 있을 경우 빈 Optional예외:
NullPointerException - 선택된 요소가 null인 경우참고:
findFirst()
findAny() 는 기본적으로 가장 처음 매칭되는 값이 return 된다는 보장이 없는 메소드이다. 그렇기때문에 순서가 중요하다면 findFirst() 을 사용하자.
TIP.
하지만 스트림이 순차적(sequential)일 경우, 대부분의 경우 첫 번째 요소나 그에 가까운 요소를 반환한다. 그러나 병렬 스트림(parallel stream)의 경우, 반환되는 요소는 어떤 특정 순서나 패턴에 따라 결정되는 것이 아니기 때문에 어떤 요소가 반환될지는 예측할 수 없다.
public class EnumValueValidator implements ConstraintValidator<EnumValueConstraint, CharSequence> {
private List<String> acceptedValues;
private Class<? extends Enum<?>> enumClass;
@Override
public void initialize(EnumValueConstraint annotation) {
acceptedValues = Stream.of(annotation.enumClass().getEnumConstants())
.map(Enum::name)
.collect(Collectors.toList());
this.enumClass = annotation.enumClass();
}
@Override
public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return acceptedValues.contains(value.toString());
}
}
해당 코드는 string 위에 붙일 시 해당 enum class 로 변환가능한지 validate 해주는 유효성 검사기 이다.
기존 로직
public class EnumValueValidator implements ConstraintValidator<EnumValueConstraint, CharSequence> {
private Class<? extends Enum<?>> enumClass;
@Override
public boolean isValid(CharSequence value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return Arrays.stream(enumClass.getEnumConstants())
.anyMatch(enumVal -> enumVal.name().equals(value.toString()));
}
}
바뀐 로직
바뀐 후 이점은?
바뀐 후 단점은?
클래스로 적어놨지만 인터페이스도 똑같음!
TIP. 인터페이스를 통해 의존 관계를 맺으면 변경에 용이해 진다. = 약한 의존관계가 된다.
고수준의 컴포넌트가 저수준 컴포넌트에 의존하지 않도록 의존관계를 역전 시키는 것.
3-tier layer 에서는 service 가 고수준, repo 가 저수준.
이유는?
그렇다면 의존역전은?
방법은? 인터페이스를 활용한다.
인터페이스는 추상적 개념을 수행하기에 고수준이다. 다른 레포들이 인터페이스를 implements 하게 된다면 오른쪽으로 흐르던 의존이 반대로 역전되면서 의존역전이 이루어진다. 더 이상 고수준이 저수준에 의존하지 않는다.
TIP. 컨트롤러는 저수준 컴포넌트! 이유는? HTTP 혹은 키보드 입출력 등 기술 종속적이기 때문.
서비스에서 레포 인스턴스를 생성하면 또 다시 의존이 생기게 된다. 그렇기 때문에 레포 인스턴스를 다른 곳에서 생성해서 서비스에 넣어줘야한다.
의존 주입패턴의 장점
어떤 인스턴스를 사용할지 코드수정 없이, 런타임에 지정 가능 하다.
런타임 = 실행하는 시점 + 실행하는 도중
실행하는 시점에 변경
실행하는 도중에 변경
public Service (List<Repository> repos) {
this.A = repos.get(0);
this.B = repos.get(1);
}
List 로 해당 되는 인터페이스 구현체를 모두 주입 받을 수 있음. 그때 우선 순위로 특정 가능.
A 를 main 레포로 사용하고 싶을때는 changeRepo() 라는 함수를 만들어, 호출 될때마다 내부에서 A 레포를 변경하는 코드를 사용할 수 있다.
public void changeRepo() {
var temp = this.A;
this.A = this.B;
this.B = temp;
}