orElse는 T객체 그 자체를, orElseGet은 람다식을 넘길 수 있다는 차이가 있다.
//orElse
UserStatistics one = statistics.stream().filter(UserStatistics::isHoldem).findFirst().orElse(new UserStatistics());
//orElseGet
UserStatistics one = statistics.stream().filter(UserStatistics::isHoldem).findFirst().orElseGet(() -> new UserStatistics());
또한 메모리/성능 상에서 차이도 있다.
orElse 는 값이 존재하여 else에 설정한 값을 반환하지 않게 될 때에도 이미 만들어 놓는다.
orElseGet은 값이 없어 진짜 필요할 때 실행된다.
따라서,
constant, static variable, or caching result 을 반환할 경우라면 orElse를 사용해도 되지만, 대량 작업을 하거나 새로운 객체를 생성해야할 경우 orElseGet를 사용하는게 좋겠다.
Member member = memberService.saveMember(snsType, token);
member.setSnsType("kakao"); /////<<<<<<<-- 여기서 실수하면 반영됨 상하로 트랜잭션이 있어서
memberService.emptyLogic();
=> dto 리턴 시 해결!
싱글톤 패턴
한 클래스마다 인스턴스를 하나만 생성하여 어디서든 참조
병렬로 여러 스레드에서 돌릴 경우 조심
직접 구현할 일 적음(스프링 빈)
장점
새로운 인스턴스를 계속해서 생성하지 않으므로 메모리 낭비가 적음
단점
코드량 많고, 테스트 어렵고, 자식 클래스 만들기 어렵, 클라이언트가 구체 클래스에 의존(dip 위반)
스프링이 모두 해결해 줌!
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
// 생성자를 private으로 적용시켜 외부에서 인스턴스 생성을 막는다
private SingletonService() {
}
}
구조 패턴
클래스나 객체를 조합하여 더 큰 구조를 만들 수 있게 해줌
어댑터 패턴
타사의 라이브러리를 호출부의 변경 없이 사용하고 싶을 경우
Array.AsList(), Collections.enumeration(), Collections.list() 등도 어댑터 패턴을 활용한 예시
@Service
@Primary //얘랑 동일한 빈이 있어도 이게 우선이다라고 해줘야(override)
public class MailAdapter implements MailSenderA {
private final MailSolutionB mailSolutionB;
@Override
public void send(MailSolutionA.MailParam mailParam) {
MailSolutionB.MailParam param = MailSolutionB.MailParam.builder().mailTitle(mailParam.getTitle())
.mailBody(mailParam.getBody()).receiverEmail(mailParam.getEmail()).build();
mailSolutionB.sendApi(param);
}
}
위 처럼 A -> B로 바꿀 때, 기존 호출부를 그대로 두고
A를 쏘는 것 처럼 보이지만 사실 B로 쏘는 것
Interface를 써야 구현체를 교체할 수 있음 그래야 primary로 오버라이딩이 가능
모든 부분을 다 고치지 않고 한 곳만 고칠 때 좋음
브릿지 패턴
기능의 구현 클래스를 런타임 때 자유롭게 지정이 가능
주입을 set을 이용하여 동적으로 받음
기능과 구현을 분리하여 구현이 변경되더라도 기능 클래스 부분에 대한 변경은 필요 없음
if (StringUtils.isNotEmpty(member.getAccountNumber())) {
paymentService.setPaymentMethod(new CardMethodService()); // <<-- 서비스 주입을 런타임에
} else if (StringUtils.isNotEmpty(member.getPhoneNumber())) {
paymentService.setPaymentMethod(new PhoneMethodService());
}
paymentService.pay1(order.getAmount(), member);
컴포지트 패턴
일반적으로 직접 구현하지 않음
클라이언트 전체와 부분을 구별하지 않고 동일한 인터페이스로 사용
트리구조와 상당히 유사한 성격을 가지고 있다
하지만 기능이 너무 다른 클래스들에는 공통 인터페이스를 제공하기 어려움
데코레이터 패턴
일반적으로 직접 구현하지 않음
기존 코드를 수정하지 않고도 행동을 확장 가능
객체를 여러 데코레이터로 래핑하여 여러 행동들을 합성 가능
데코레이터를 너무 많이사용하면 코드가 필요 이상으로 복잡해짐
file, buffered reader 등등 New로 만들어서 래핑해서 합성하는 것
퍼사드 패턴
복잡한 로직들을 숨기고 간단한 인터페이스 함수만을 호출(추상화)
우리는 이미 MVC패턴을 통해서 퍼사드 패턴을 알게 모르게 사용하고 있다.
플라이웨이트 패턴
일반적으로 직접 구현하지 않음
인스턴스가 필요할 때마나 매번 생성하는게 아니라 가능한 공유해서 메모리를 절약
캐시, 레디스.. 디비 접근 최소화
String 객체를 생성하는 리터럴 방식의 String Constant Pool이 대표적인 플라이웨이트 패턴을 적용한 예시
@Test
void 스트링_플라이웨이트패턴_테스트() {
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
// 서로 같다!
Assertions.assertSame(str1, str2);
// 서로 다르다!
Assertions.assertNotSame(str1, str3);
// 서로 같다!
Assertions.assertSame(str1, str3.intern());
/*
클래스가 JVM에 로드되면 모든 리터럴은 constant pool에 위치하게 된다.
그리고 리터럴을 통해 같은 문자를 생성한다면 풀 안의 같은 상수를 참조하게 되는데 이를 String interning이라고 한다.
String을 리터럴로 생성될 때 intern()이라는 메서드가 호출되고 이 intern() 메서드는
constant pool에 같은 문자가 존재하는지 확인 후 존재한다면 그 참조 값을 가지게 된다.
*/
}
@Test
void Integer_플라이웨이트패턴_테스트() {
Integer integer1 = Integer.valueOf("123");
Integer integer2 = Integer.valueOf("123");
// 서로 같다!
Assertions.assertSame(integer1, integer2);
Integer integer3 = Integer.valueOf("128");
Integer integer4 = Integer.valueOf("128");
// 서로 다르다!
Assertions.assertNotSame(integer3, integer4);
//직접 Integer.valueOf 함수 까보기!
//-128 ~ 127 까지는 캐싱이라 같고 그 이외 값은 캐싱안하고 새롭게 인스턴스 생성이라 다름
}
프록시 패턴
SRP와 연관
공통되는 로직 빼서 모듈화
AOP(aspect oriented programming; cglib etc..)
OOP로 독립적으로 분리하기 어려운 부가 기능을 모듈화 하는 방식
oop를 더 oop스럽게 해주는 방법
객체지향을 더 객체지향스럽게 만들어 주는 프로그래밍
transactional, cache, test에 사용하는..
spring 큰 특징 : DI, AOP(aspect), PSA(portable service abstraction)
import com.example.pattern.order.service.OrderService;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.objenesis.SpringObjenesis;
@Configuration
public class ProxyConfig {
private final SpringObjenesis objenesis = new SpringObjenesis();
@Bean
@Primary //order service말고 프락시를 빈으로 등록하도록 해야 aop 적용
public OrderService orderServiceProxy() {
return (OrderService) createCGLibProxy(OrderService.class, new MethodCallLogInterceptor());
}
private Object createCGLibProxy(Class<? extends Object> targetClass, MethodInterceptor interceptor) {
// Create the proxy
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(interceptor);
return enhancer.create();
}
}
행동 패턴
반복적으로 사용되는 객체들의 커뮤니케이션을 패턴화, 결합도를 최소화 하는 것이 목적
책임 연쇄 패턴
스프링 필터 사용(필터 체인)
검증 절차는 자유롭게 추가될 수 있어야 하고, 순서도 자유롭게 변경할 수 있어야 할 때
기존 클라이언트 코드를 수정하지 않고 앱에 새 핸들러를 도입 가능
@Configuration
public class FilterConfig {
// @Bean
// @Order(0)
public FilterRegistrationBean userAgentCheckFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(new UserAgentCheckFilter());
registrationBean.addUrlPatterns("/orders/*");
return registrationBean;
}
// @Bean
// @Order(1)
public FilterRegistrationBean memberCheckFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(new MemberCheckFilter());
registrationBean.addUrlPatterns("/orders/*");
return registrationBean;
}
}
커맨드 패턴
잘 사용하지 않음
기존 클라이언트 코드를 수정하지 않고 새로운 커맨드들을 도입 가능
요청부와 동작부를 분리시켜 주기 때문에 결합도를 낮출 수 있음
보통 비동기
@Test
void 혜택_발송_스레드_테스트() throws InterruptedException {
int numberOfThreads = 5;
CountDownLatch latch = new CountDownLatch(numberOfThreads);
//ExecutorService 객체가 Invoker를 의미
ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
for (int i = 0; i < numberOfThreads; i++) {
//Runnable 구현 객체가 Command를 의미;
// runnable interface 특징? 구현할 함수가 1개, callable과 디르게 반환타입이 없음
//CouponApiService 객체가 Receiver를 의미
Runnable doThread = new CouponService(new CouponApiService());
//위 대신 lambda로 대체가능
executorService.execute(() -> {
doThread.run();
latch.countDown();
});
}
latch.await();
}
Runnable -> void
Callable -> return 있음
반복자 패턴
잘 사용하지 않음
혜택이 아이템을 어떻게 구현하였는지 호출하는 쪽에서는 알 필요가 없음
즉 캡슐화가 잘 되어 있음
내부 구현을 외부로 노출시키지 않으면서도 모든 항목에 접근 가능
적용 시 덜 효율적이거나 과도하지는 않은지 확인
옵저버 패턴
한 객체의 상태가 변경되어 다른 객체들을 변경해야 할 필요성이 생겼을 때 사용(Pub/Sub 패턴이라고도 함)
message push, pull 하는 방식을 통해 결합도를 낮출 수/없앨 수 있음
rabbitMQ, kafka 등
느슨한 결합으로 객체간의 의존성 제거
너무 많이 사용하게 되면 상태 관리가 힘듦
private final ApplicationEventPublisher eventPublisher;
//함수 내부에서 호출 publish
eventPublisher.publishEvent(new OrderEvent(order.getId(), OrderType.CREATE));
//
import com.example.pattern.order.model.OrderEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@Async
public class OrderListener {
@EventListener
public void onApplicationEvent(OrderEvent event) {
log.info("주문이벤트 Published [orderId : {}, orderType : {}]", event.getOrderId(), event.getOrderType().toString());
log.info("이후 리뷰 알림 발송이 시작됩니다.");
log.info("이후 정산 API 호출이 시작됩니다.");
}
}
중재자 패턴
옵저버 패턴
1개의 publish에 대해 N개의 subcriber가 존재하고, observer가 pulling 이나 push방식을 통해 관리
중재자 패턴
M개의 publisher와 N개의 subcriber 사이에서 1개의 mediator를 통해 통신
메멘토 패턴
상태정보 저장 관련 메모리에 들고 있을 일 없음; 잘 사용하지 않음
캡슐화를 위반하지 않고 객체의 state 스냅샷을 생성할 수 있음
메멘토를 너무 자주 생성하면 많은 Ram을 소모할 수 있음
상태 패턴
잘 사용하지 않음
전략 패턴
하나의 메시지와 책임을 정의하고 이를 수행할 수 있는 다양한 전략을 만든 후, 다형성을 통해 전략을 선택해 구현을 실행
유사한 패턴들
팩토리, 추상 팩토리, 브릿지, 커맨드 패턴 등 이미 사용 중
특징
OCP 준수, 하지만 과도하게 복잡해질 수 있음
템플릿 메서드 패턴
잘 사용하지 않음, 요즘은 implement
interface는 내부 instance를 둘 수 없음 -> 필요 시 abstract class 를 사용
상속을 통해 슈퍼 클래스의 기능을 확장할 때 사용하는 대표적인 방법
변하지 않는 기능은 슈퍼 클래스에 만들어 두고, 확장할 기능은 서브 클래스에서 만듦
단점
알고리즘 변경 시 거의 모든 클래스에 수정이 가해질 수 있음
상속의 단점.. 결합이 커서
public abstract class Review { //abstract 사용예시
public void review() {
//부모 클래스에서 알고리즘의 골격을 정의
login();
selectBooks();
putContent();
selectEvaluation();
}
public void login() {
log.info("로그인 성공!");
}
public void selectBooks() {
log.info("리뷰 대상 상품 선택");
}
public void selectEvaluation() {
log.info("별점 선택");
}
public abstract void putContent(); //
}
방문자 패턴
잘 사용하지 않음
맴버는 맴버 클래스로, 리워드는 리워드 클래스로.. 둘을 섞지 않는다
OCP : 다른 클래스를 변경하지 않으면서 새로운 행동 도입 가능
SRP : 같은 행동의 여러 버전을 같은 클래스로 이동할 수 있음
public class PointBenefit implements Benefit {
@Override
public void getBenefit(GoldMember member) {
log.info("골드 멤버를 위한 포인트 제공 혜택");
}
@Override
public void getBenefit(VIPMember member) {
log.info("VIP 멤버를 위한 포인트 제공 혜택");
}
@Override
public void getBenefit(SilverMember member) {
log.info("실버 멤버를 위한 포인트 제공 혜택");
}
}
public class GoldMember implements Member {
// public void point() {
// log.info("골드 멤버를 위한 포인트 제공 혜택");
// }
// public void discount() {
// log.info("골드 멤버를 위한 할인 혜택");
// }
@Override
public void getBenefit(Benefit benefit) {
benefit.getBenefit(this);
}
}
stream을 사용하여 리스트의 홀수번째(index 기준 0, 2, 4)에 있는 원소를 콘솔에 찍어본다고 가정하자.
아래와 같이 짜야지라고 쉽게 생각할 수 있다.
variable used in lambda expression should be final or effectively final
근데 위와 같은 에러가 난다.
그 이유는 아래와 같다. 자세한 내용을 알려면 람다 캡쳐링과 그 원리에 대해 이해해야한다.
The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems.
람다식 내부에서는 람다식 안에서 정의된 변수가 아닌 외부 변수에 접근할 수 있는데 이를 람다 캡쳐링이라고 한다.
스트림은 여러 thread의 병렬처리를 염두하고 만들어졌다. 즉 별도의 스레드에서 실행할 수 있다. 그렇다면 어떻게 기존 스레드의 값을 참조하여 쓸 수 있을까? 기존 스레드의 작업이 종료되었을 수도 있는데 말이다.
람다 캡쳐링이 일어날 때 데이터의 참조값(call by reference)이 아닌 데이터 값 그 자체(call by value)를 복사하여 자신의 스택에 두고 작업을 한다. 그렇기 때문에 값이 변경될 여지가 있는 변수는 사용할 수 없고 final에 준하는(effectively final) 변수만 람다 안에서 사용할 수 있다.
반면 heap에 저장된 값은 thread끼리 공유하고 있기 때문에 언제든지 구할 수 있으므로 변경하더라도 에러가 나지 않는다.
Java의 스트림 API에서 외부 변수를 사용할 때 해당 변수가 effectively final해야 하는 이유는 다음과 같습니다:
1. 스레드 안전성
스트림의 연산은 종종 병렬로 실행되며, 외부 변수가 여러 스레드에서 동시에 접근될 수 있습니다. 이를 방지하기 위해 외부 변수가 변경되지 않도록 보장해야 합니다.
2. 불변성
외부 변수가 effectively final이면, 그 값이 변경되지 않는 것을 보장합니다. 이로 인해, 람다 표현식이나 메서드 참조가 이 변수를 안전하게 사용할 수 있습니다. 불변성을 유지함으로써 예측 가능한 동작을 보장합니다.
3. 람다 캡처
Java의 람다 표현식은 외부 변수를 캡처할 수 있지만, 캡처된 변수는 내부적으로 복사되어 사용됩니다. 만약 이 변수가 변경 가능하다면, 예상치 못한 결과를 초래할 수 있습니다. 이를 방지하기 위해 effectively final 조건이 필요합니다.`
위 함수의 에러는 여러 방법으로 수정할 수 있다.
1. AtomicInteger를 사용하여 수정
2. list/array를 이용해 수정
2번 방식으로 수정하다 보니 신기했던 것은 단순 값이 변경된다는 것에 초점을 둘게 아니라 final이면 된다는 점에 초점을 뒀어야 한다는 것이다. 일반적으로 final이라고 하면 불변, 즉 string이나 int인 경우 값이 바뀌면 안 된다고 인식하기 때문에 '값의 변경'에 나도 모르게 초점이 갔는데, collection 같은 경우에는 final이어도 값이 변동(추가 혹은 수정)될 수 있다!
전체 변경; 재할당일부 변경
즉 참조값이 바뀌면 안 되고 같은 참조값 안에서의 변경은 된다(final but mutable)
final --> You cannot change the reference to the collection (Object). You can modify the collection / Object the reference points to. You can still add elements to the collection
immutable --> You cannot modify the contents of the Collection / Object the reference points to. You cannot add elements to the collection.
List<Student> studentList = new ArrayList<>();
studentList.add(a);
studentList.add(b);
studentList.add(c);
for (int i = 0; i < studentList.size(); i++) {
System.out.println("Roll number: " + studentList.get(i).getRollNumber());
}
advanced for loop
for (Student st : studentList) {
System.out.println("Roll number: " + st.getRollNumber());
}
while, hasNext
Iterator<Object> it = list.iterator();
while (it.hasNext()) {
Object o = it.next();
// do stuff
}
old for
advanced for
since
jdk1
jdk5
index increasing
custom가능(2씩 증가 등); 역순 가능
무조건 1씩 증가만 가능; 역순 불가
index approach
index 접근 가능
index 접근 불가
usage
어떠한 셀 수 있는 container object에 사용 가능
iterable interface를 구현한 구현체만 사용가능
위 세 방법 모두 성능상에 큰 차이는 없고, 굳이 따지자면 old for loop이 index의 객체를 탐사해야 하니(Object.get(i)) 조금 더 느릴 수 있다는 글이 있다. advance for loop으로 짜면 컴파일러가 while hasNext 문으로 변환할 거라 두 방법은 사실 거의 같은 거라고 볼 수 있다.
단순 1씩 증가한 loop이라면 advanced for loop을 사용하는 게 보기에도 더 좋을 듯하다.
구관이 명관인가. (의외로) old for loop이 제일 성능이 좋았다.. 다른 것보다 거의 두배 정도?(정확히 두배라고 말할 순 없지만..) 그다음이 while문이라니.. 나머지는 비슷비슷한 것 같은데 어쨌건 꼴찌는 Collection.foreach였다..ㅋㅋㅋ 어느 글을 믿어야 하나,, 신기방기 하다.
단순한 자바 성능 테스트를 하기 위한 툴로 benchmark가 있다. 간단하게 세팅하고 실행해본다.
환경: java 11 / gradle 7.4
1. intellij에서 gradle로 프로젝트 생성
2. 파일 구조 아래와 같이 만들기
main/test 폴더 삭제
3. build.gradle에 plugin 추가
plugins {
id 'java'
id "me.champeau.jmh" version "0.6.6"
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
}
test {
useJUnitPlatform()
}
powermock이란? junit4로 테스트코드를 작성 시 static method를 mock할 수 있게 하는 방법
Mockito Mock Static Method using PowerMock PowerMock provides different modules to extend Mockito framework and run JUnit and TestNG test cases.
Note that PowerMock doesn’t support JUnit 5 yet, so we will create JUnit 4 test cases. We will also learn how to integrate TestNG with Mockito and PowerMock.
public static DataHandler getDataHandler() throws IllegalStateException {
...
}
We need to do the following to integrate PowerMock with Mockito and JUnit 4.
1. Annotate test class with @RunWith(PowerMockRunner.class) annotation. 2. Annotate test class with @PrepareForTest and provide classed to be mocked using PowerMock. 3. Use PowerMockito.mockStatic() for mocking class with static methods. 4. Use PowerMockito.verifyStatic() for verifying mocked methods using Mockito.
powermockito가 기존의 junit4와 혼용이 가능한지 궁금했는데, junit4코드를 아래와 같이 수정하니 잘 되는 것을 확인할 수 있었다.
@RunWith(PowerMockRunner.class) //1.
@PrepareForTest(DataHandlerFactory.class) //2.
//@RunWith(MockitoJUnitRunner.class)
public class HuntBOTest {
//기존 junit4 스타일로 선언
@InjectMocks
private HuntBO huntBO;
@Mock
private HuntDAO huntDAO;
public String ALPHA_FILE = "data-alpha.xml";
public void includeForAlpha(){
//mockStatic 이후에 하면 null이 나오기 때문에 mock하기 전에 stub을 만들어야 한다.
DataHandler sample = DataHandlerFactory.create(ALPHA_FILE);
PowerMockito.mockStatic(DataHandlerFactory.class); //3.
when(DataHandlerFactory.getDataHandler()).thenReturn(sample);
}
@Test
public void getTop1000__subList__alpha(){
includeForAlpha();
when(huntDAO.selectHuntTop1000AfterAug()).thenReturn(getList());
List<HuntRank> hrList = huntBO.getTop1000();
List<HuntRank> hrListSub = hrList.subList(0, 1);
assertThat(hrListSub.get(0).getRank(), is(1));
}
}