2021 02 16
2021-02-16¶
학습 Log¶
- 스스로 기록하는 학습 이력 - 학습한 내용에 가중치를 부여해 미션에 어떤 공부를 중점적으로 했는지 기록
TDD¶
- TDD란? - 테스트를 먼저 개발하기 - 동작하는 production 코드 만들기 - 리팩토링 진행하기 - 각 테스트 별로 필요한 만큼 구현할 것
- TDD의 장점 - 자동화된 테스트 + 배포 부담을 줄이면서 Business Logic에 집중
일급 컬렉션¶
- 참고: https://jojoldu.tistory.com/412
- 객체지향 생활체조 (규칙 8: 일급 컬렉션 사용) - 컬렉션을 포함한 클래스는 반드시 다른 멤버 변수가 없어야 한다. - 그 자체로 포장되어 있으므로, 콜렉션과 관련된 동작은 근거지가 마련된 셈이다. - 두 그룹을 같이 묶는 다던가, 그룹의 각 원소에 규칙을 적용하는 등의 동작을 처리할 수 있다.
- 정의 - Collection을 Wrapping하면서, 그 외 다른 멤버 변수가 없는 상태
- 장점
- [비즈니스에 종속적인 자료구조] - 검증 조건이 필요한 모든 곳에 검증을 해왔던 기존 방식 - 해당 조건으로만 생성할 수 있는 자료구조를 만들자!- [Collection의 불변성을 보장] - 불변의 중요성: 절대 값이 바뀔 일이 없다 == 사이드 이펙트 최소화 - final은 재할당만 금지 - 컬렉션의 값을 변경할 수 있는 메소드가 없다면? -> 불변 컬렉션 - List라는 컬렉션에 접근할 수 있는 방법이 없기 때문에 값 변경/추가가 안된다!
- [상태/행위를 한 곳에서 관리] - 값과 로직이 함께 존재 - ex) 컬렉션과 계산 로직이 관계가 있는데, 코드로 표현이 안 될 수 있다. - 똑같은 기능을 하는 메서드 중복 생성 가능 - 계산 메서드 누락할 수도
- [이름이 있는 컬렉션] - 그냥 List 자료구조 였다면... - 검색이 어렵다 - 변수명으로만 검색 가능 - 변수명은 개발자마다 다르게 지음 - 명확한 표현이 불가능 - 변수명이기에 의미를 부여하기 어렵다 - 일급 컬렉션을 통해 컬렉션 기반으로 용어사용 과 검색 지원한다
BigDecimal¶
- 참고 1: http://javamoods.blogspot.com/2009/03/how-big-is-bigdecimal.html
- 참고 2: https://www.geeksforgeeks.org/bigdecimal-class-java/
- 참고 3: https://jsonobject.tistory.com/466
- 필요성 - double형 변수에 대해 연산, 자릿수 조정, 반올림, 비교, 형 변환, 해싱 등에 대해 연산 제공 - 아주 큰/아주 작은 부동 소숫점 연산을 아주 정확하게 수행할 수 있다. (시간을 조금 희생한다) - Java에서 숫자를 정밀하게 저장하고 표현하는 유일한 방법 - 돈/소수점을 다룬다면 필수! - 내부적으로 수를 십진수로 저장하여, 거의 무한한 정밀도를 보장!
- 구조 - 정수x10^(-scale) 로 표현
- 용량 - double: 8 bytes - Double: 16 bytes (8 bytes overhead for the class, 8 bytes for the double) - BigDecimal: 32 bytes
- 성능 - Java5 기준, 백만번의 연산을 수행 시, BigDecimal 연산이 double에 비해 3~4배 느림
- 기본 용어 - precision: 숫자를 구성하는 전체 자릿 수 (0 제외) - ex) 12345 --precision--> 5 - ex) 012345.67890 --precision--> 9 - scale: 전체 소수점 자리수, BigDecimal은 32bit의 소수점 크기를 가진다 - ex) 012345.67890 --scale--> 4 - ex) 0.00 --scale--> 1 - Decimal128: 34자리까지 표현 가능한 10진수를 저장 - 기본형 변환 메서드: Number로 부터 상속받음 - int intValue(); - long longValue(); - float floatValue(); - double doubleValue(); - 변환한 결과가 변환한 타입의 범위에 속하지 않는다면: ArithmeticException 발생 - byte byteValueExact(); - short shortValueExact(); - int intValueExact(); - long longValueExact(); - BigInteger toVigIntegerExact();
- 초기화 - new BigDecimal("0.001"); - String을 넘겨줘서 초기화를 해주자! - double 타입을 넘겨준다면 이진수의 근사치를 넘겨줘 예상과 다른 값이 나온다!
정적 팩토리 메서드¶
- 참고 1: https://johngrib.github.io/wiki/static-factory-method-pattern/
- 참고 2: https://velog.io/@ljinsk3/%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9C%EB%8A%94-%EC%99%9C-%EC%82%AC%EC%9A%A9%ED%95%A0%EA%B9%8C
- 정의 - 객체 생성의 역할을 하는 클래스 메서드 - 객체 생성을 캡슐화하는 기법 - 객체 생성하는 메서드를 만들고, static으로 선언하는 것
- 장점
- [이름이 있어, 가독성이 좋아진다]
- ex) Character warrior = Character.newWarrior();
- [호출할 때마다 새로운 객체를 생성하지 않아도 된다] - 불변 객체를 캐시해서 사용중이라면, new 연산을 사용 안해도 된다. - private으로 생성자를 설정하면, 정해진 범위를 벗어나는 로또 번호의 생성을 막을 수 있다. - ex)
- [하위 자료형 객체를 반환할 수 있다] - 생성자와 다르게 정적 팩토리 메서드는 리턴할 객체를 정할 수 있다 - 분기처리를 통해 하위 타입의 객체를 반환할 수 있다. - ex)
public static Discount createDiscountItem(String discountCode) { if (isUsableCoupon(discountCode)) { return new Coupon(1000); } if (isUsablePoint(discountCode)) { return new Point(500); } }- [객체 생성을 캡슐화 할 수 있다] - DTO와 Entity간에 자유롭게 형 변환이 가능하도록 변경 가능하다. - 내부 구현을 모르더라도 쉽게 변환할 수 있다. - ex) CarDto carDto = CarDto.from(car);
- 단점 - 하위 클래스를 못 만든다 - 다른 정적 메서드와 잘 구분되지 않는다
- 흔히 사용되는 이름 - from: 하나의 매개변수를 받아, 객체를 생성 - of: 여러개의 매개변수를 받아, 객체를 생성 - getInstance: 인스턴스를 생성 - newInstance: 새로운 인스턴스를 생성
stream()¶
- 2.1 스트림이란?
- [정의]
- 데이터 소스를 추상화하고, 데이터를 다루는데 자주 사용되는 메서드들을 정의
- 데이터 소스가 무엇이던 같은 방식으로 다루며, 코드의 재사용성을 높인다
- [스트림은 작업을 내부 반복으로 처리한다] - 반복문을 메서드의 내부에 숨길 수 있다는 것 - 조건문도 메서드 내부에 숨길 수 있다
- [스트림의 연산] - 중간 연산은 연산결과를 스트림으로 반환, 연속해서 연결 가능 - 최종 연산은 스트림의 요소를 소모하면서 연산을 수행, 단 한번의 연산
- [지연된 연산] - 최종 연산이 수행되어야 비로소 스트림의 요소들이 중간 연산 거쳐 최종 연산에서 소모
- [Stream
와 IntStream] - 오토박싱&언박싱으로 인한 비효율을 줄이기 위해 데이터 소스의 요소를 기본형(int, long, double)으로 다루는 스트림 제공 - IntStream, LongStream, DoubleStream- [병렬 스트림] - 병렬 처리에 용이함 - .parallel() 메서드를 호출하도록 하자
- 2.2 스트림 만들기
- [컬렉션]
- 컬렉션의 최고 조상인 Collection에 Stream()이 정의되어 있음
- Collection의 자손인 List와 Set에서 사용 가능
- [배열] - Stream과 Arrays에 static 메서드로 정의되어 있음 - ex1) Stream.of("a", "b", "c"); - ex2) Stream.of(new String[]{"a", "b", "c"}); - ex3) Arrays.stream(new String[]{"a", "b", "c"});
- [특정 범위의 정수] - IntStream과 LongStream은 지정된 범위의 연속된 정수를 스트림으로 생성하는 range(), rangeClosed() 지원 - IntStream.range(int begin, int end); - IntStream.rangeClosed(int begin, int end);
- [임의의 수] - 난수 생성하는 Random 클래스에서 다음과 같은 인스턴스 메서드 지원 - IntStream ints() - LongStream longs() - DoubleStream doubles() - 해당 메서드들은 무한 스트림이라, 스트림 크기를 제한해야 한다. - 매개변수로 스트림의 크기를 지정할 수 있다. - 지정된 범위의 난수를 발생시킬 수 있다.
IntStream intStream = new Random().ints(); intStream.limit(5).forEach(System.out::println); IntStream intStream = new Random().ints(5); //크기가 5인 난수 스트림 IntStream intStream = new Random().ints(6, 1, 46); //크기가 6인 1~45 사이의 난수 스트림- [람다식 - iterate(), generate()]
- iterate() - seed로 지정된 값부터, 람다식 f에 의해 계산된 결과를 다시 seed값으로 해서 계산을 반복 - ex) StreamevenStream = Stream.iterate(0, n -> n+2); //0, 2, 4, 6... - generate() - 람다식에 의해 계산되는 값을 요소로 하는 무한 스트림이지만, 이전 결과 이용해 계산은 X - ex) Stream oneStream = Stream.generate(() -> 1); //1, 1, 1, 1... - [빈 스트림] - 요소가 하나도 없는 비어있는 스트림 생성 가능 - 연산 수행 후 결과가 하나도 없다면, null이 아닌 빈 스트림 반환하자
- [두 스트림의 연결] - 스트림의 static 메서드인 concat() 사용하면, 두 스트림을 하나로 연결 가능 - 두 스트림의 요소는 같은 타입일 것
- 2.3 스르팀의 중간연산
- [스트림 자르기 - skip(), limit()]
- skip(3): 3개의 요소 건너뛰기
- limit(5): 스트림 요소 5개로 제한
IntStream intStream = IntStream.rangeClosed(1, 10); // 1~10 스트림 intStream.skip(3).limit(5).forEach(System.out::print); // 45678- [스트림의 요소 걸러내기 - filter(), distinct()] - distinct(): 중복된 요소 제거 - filter(): 주어진 조건에 맞는 요소만 남김
IntStream intStream = IntStream.of(1, 2, 2, 3, 3, 3, 4, 5, 5, 6); intStream.distinct().forEach(System.out::print): // 123456 IntStream intStream = IntStream.rangeClosed(1, 10); // 1~10 intStream.filter(i -> i%2 == 0).forEach(System.out::print); // 246810- [정렬 - sorted()] - sorted(): 정렬할 때 사용 - comparator를 구현하여 적용할 수도 있음. 추후 학습 예정
Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b"); strStream.sorted().forEach(System.out::print); // CCaaabccdd- [변환 - map()] - map(): 스트림의 요소에 저장된 값 중 원하는 필드 뽑아내거나, 특정 형태로 변환할 때 사용
Stream<File> fileStream = Stream.of(new File("Ex1.java"), new File("Ex2.java")); Stream<String> fileNameStream = fileStream.map(File::getName); fileNameStream.forEach(System.out::println); // Ex1.java Ex2.java- [조회 - peek()] - peek(): 연산과 연산 사이에 올바르게 처리가 되었는지 확인
- [mapToInt(), mapToLong(), mapToDouble()] - Stream
타입의 스트림을 기본형 스트림으로 변환할 때 사용 - 기본형 스트림은 숫자를 다루는 메서드들이 지원됨 - sum(): 스트림 요소의 총합 - average(): sum() / (double)count() - max(): 스트림 요소 중 제일 큰 값 - min(): 스트림 요소 중 제일 작은 값
- 2.4 Optional
와 OptionalInt - 정의 - 최종 연산의 결과 타입이 Optional인 경우가 있다 - Optional은 지네릭 클래스로 'T타입의 객체'를 감싸는 래퍼 클래스 - value를 가리키는 참조형을 반환 - null을 가리켜도 반환받은 것은 참조값이라 Exception이 안 뜬다!
- 반환된 결과가 null인지 매번 if문으로 확인하지 않고, 정의된 메서드를 사용한다 - if문 없이도 NullPointerException 발생하지 않는 간결하고, 안전한 코드 작성 가능- [Optional 객체 생성하기] - of(): Optional 객체 생성 - ofNullable(): 참조 변수의 값이 null일 가능성이 있다면 사용
- [Optional 객체의 값 가져오기] - get(): 저장된 값 가져오기, null이면 예외 발생 - orElse(""): 저장된 값이 null이면, ""를 반환 - orElseGet(() -> new String()): 저장된 값이 null이면, new String()을 반환 - orElseThrow(NullPointerException::new): null이면 예외 발생
- [Optional 객체에 사용되는 filter(), map()] - Optional 객체의 값이 null 이면, 아무 일 하지 않음
int result = Optional.of("123") .filter(x->x.length()>0) .map(Integer::parseInt).orElse(-1); // result = 123 int result = Optional.of("") .filter(x->x.length()>0) .map(Integer::parseInt).orElse(-1); // result = -1- [OptionalInt, OptionalLong, OptionalDouble] - 기본형을 값으로 하는 Optional
- 2.5 스트림의 최종연산
- 특징
- 스트림의 요소를 소모해 결과를 만들어냄
> 최종 연산 이후의 스트림은 사용할 수 없다!!
- 결과는 스트림 요소의 합과 같은 단일 값, 스트림의 요소가 담긴 배열, 컬렉션 등- [forEach()] - 반환 타입이 void, 하나씩 수행
- [조건 검사 - allMatch(), anyMatch(), nonMatch(), findFirst(), findAny()] - 스트림의 요소에 대해 지정된 조건에 "모두 일치" / "일부 일치" / "하나도 안 일치" 검증 - 매개변수 Predicate, 연산결과 boolean 반환 - 스트림의 요소 중 조건에 맞는 첫 번째 것 반환하는 findFirst(), findAny() //병렬 스트림일시 사용 - 주로 .filter()와 함께 쓰인다
boolean noFailed = stuStream.anyMatch(s->s.getTotalScore() <= 100); Optional<Student> stu = stuStream.filter(s->s.getTotalScore()<=100).findFirst();- [통계 - count(), sum(), average(), max(), min()] - 기본형 스트림이 아닐 경우, count() / min() / max() 만 가능
- [리듀싱 - reduce()] - 스트림의 요소를 줄여나가면서 연산을 수행, 최종 결과를 반환 - sum()의 구현은 reduce()로 되어있다
- 2.6 collect()
- 정의
- 스트림의 최종 연산 중 하나
- reduce()와 유사
- Collector 인터페이스를 구현한 것
- Collectors 클래스는 다양한 static 메서드를 통해 기능 제공
- [스트림을 컬렉션과 배열로 변환 - toList(), toSet(), toMap(), toCollection(), toArray()] - 스트림의 모든 요소를 컬렉션에 수집하기 - List, Set, Map이 아닌 특정 컬렉션을 지정하려면, toCollection()에 해당 컬렉션의 생성자 참조를 매개변수로 넣자
List<String> personName = persons.stream() .map(person -> person.getName()) .collect(Collectors.toList()); Map<Integer, String> personInfo = persons.stream() .collect(Collectors.toMap(person -> person.getId(), person -> person.getName()));- [통계 - counting(), summingInt(), averagingInt(), maxBy(), minBy()] - 최종 연산으로도 제공되지만, collect()를 사용하여 얻을 수도 있다. - 이는 collect()는 그룹을 나눠서 계산이 가능하기 때문
long count = persons.stream().collect(counting()); int count2 = persons.stream().collect(summingInt(Person::getId));- [리듀싱 - reducing()] - 최종 연산으로 제공되는 reduce()를 collect()를 통해 얻을 수 있다.
- [문자열 결합 - joining()] - 문자열 스트림의 요소를 하나의 문자열로 연결하여 반환한다
- [그룹화와 분할 - groupingBy(), partitioningBy()] - groupingBy(): 스트림을 n분할 하기 - partitioningBy(): 스트림을 2분할 하기