가마불이 꺼졌다가 다시 태우니 투명유와 유백유는 신기한 무늬가 생겼다. (지금 다시보니 청유같아보이기도...)
흑유 환원소성
투명(넓은),유백(좁은) 그릇
'분류 전체보기'에 해당되는 글 19건
- 2019.04.19 그릇된흙 신기한 무늬, 발색
- 2018.12.24 컵
- 2017.04.25 [도서] Java8 in Action - 새로운 날짜/시간 API
- 2017.04.25 [도서] Java8 in Action - Stream
- 2015.07.28 암호화, 해시 그리고 인코딩
- 2015.06.18 serialize와 serialVersionUID
- 2015.05.14 Static, Final Method Unit Test
- 2015.05.14 Maven test와 junit의 동작 차이 (@Ignore annotation이 다르게 동작해요)
- 2014.11.25 [도서] 가장 쉬운 데이터베이스 설계 책
- 2014.10.29 해물파전
Java8 in Action 정리
- Date
- 1900년을 기준으로 하는 오프셋
- 0으로 시작하는 달 인덱스
- 가변 객체
- Calendar
- Date 와의 호환성 유지 및 개선을 위해 나온 클래스
- 오프셋 이슈는 해결했으나 0으로 시작하는 달 인덱스는 유지됨
- 가변 객체
- DateFormat
- Date 클래스에만 작동
- 스레드 세이프하지 않음.
- LocalDate
- 시간을 제외한 날짜만 표현하는 불변객체
- LocalDate today = LocalDate.now(); // 시스템시계정보를 이용한 현재 날짜정보 획득
LocalDate date = LocalDate.of(2016, 3, 13); // 2016-03-13
int year = date.getYear(); // 2016
Month month = date.getMonth(); // MARCH
int day = date.getDayOfMonth(); // 13
DayOfWeek dow = date.getDayOfWeek(); // SUNDAY
int len = date.lengthOfMonth(); // 31 (3월의 일수)
boolean leap = date.isLeapYear(); // true (윤년)
// TemporalField를 이용한 값 조회
int year_ = date.get(ChronoField.YEAR); // 2016
int month_ = date.get(ChronoField.MONTH_OF_YEAR); // 3
int day_ = date.get(ChronoField.DAY_OF_MONTH); // 13
int dayOfYear = date.getDayOfYear(); // 73
- LocalTime
- 시간정보만을 갖는 객체
- LocalTime time = LocalTime.of(13, 45, 20); // 13:45:20
int hour = time.getHour(); // 13
int minute = time.getMinute(); // 45
int second = time.getSecond(); // 20
LocalTime parseTime = LocalTime.parse("13:45:20");
- LocalDateTime
- LocalDate와 LocalTime을 쌍으로 갖는 복합 클래스
- LocalDate localDate = LocalDate.of(2016, Month.MARCH, 13);
LocalTime localTime = LocalTime.of(13, 45, 20);
LocalDateTime dt1 = LocalDateTime.of(2016, Month.MARCH, 13, 13, 45, 20); // 2016-03-13 13:45:20
LocalDateTime dt2 = LocalDateTime.of(localDate, localTime);
LocalDateTime dt3 = localDate.atTime(13, 45, 20);
LocalDateTime dt4 = localDate.atTime(localTime);
LocalDateTime dt5 = localTime.atDate(localDate);
LocalDate date = dt1.toLocalDate();
LocalTime time = dt1.toLocalTime();
- Instant
- 유닉스 에포크 시간(1970-01-01 00:00:00 UTC)을 기준으로 특정 지점까지의 시간을 초로 표현
- 나노초의 정밀도를 제공
- Instant now = Instant.now();
Instant i1 = Instant.ofEpochSecond(3); // 1970-01-01T00:00:03Z
Instant i2 = Instant.ofEpochSecond(3, 0); // 1970-01-01T00:00:03Z
Instant i3 = Instant.ofEpochSecond(2, 1000000000L); // 2초 이후의 1억 나노초(1초), 1970-01-01T00:00:03Z
Instant i4 = Instant.ofEpochSecond(4, -1000000000L); // 4초 이전의 1억 나노초(1초), 1970-01-01T00:00:03Z
- Duration 과 Period
- LocalDateTime은 사람이 사용하도록, Instant 는 기계가 사용하도록 만들어진 클래스로 두 인스턴스를 혼합해서 사용할 수 없음
- Duration 클래스는 초와 나노초로 시간단위를 표현
- Period는 년, 월, 일로 시간을 표현할 때 사용
- Duration과 Period 클래스가 공통 제공하는 메서드
- beetween : 두 시간 사이의 간격을 생성
- from : 시간 단위로 간격을 생성
- of : 주어진 구성 요소에서 간격 인스턴스를 생성
- parse : 문자열을 파싱해서 간격 인스턴스를 생성
- addTo : 현재값의 복사본을 생성한 후 지정된 Temporal 객체에 추가
- get : 현재 간격 정보값을 읽음
- isNegative : 간격이 음수인지 확인
- isZero : 간격이 0인지 확인
- minus : 현재값에서 주어진 시간을 뺀 복사본을 생성
- multipliedBy : 현재값에 주어진 값을 곱한 복사본을 생성
- negated : 주어진 값의 부호를 반전한 복사본을 생성
- plus : 현재값에 주어진 시간을 더한 복사본을 생성
- subtractFrom : 지정된 Temporal 객체에서 간격을 뺌
- 특정시점을 표현(조정)하는 날짜 시간 클래스의 공통 메서드
- LocalDate.of(2014, 3, 18).withYear(2011).withDayOfMonth(25).with(ChronoField.MONTH_OF_YEAR, 9);
- 2014-03-18 > 2011-03-18 > 2011-03-25 > 2011-09-25
- 공통 메서드
- from : 주어진 Temporal 객체를 이용해서 클래스의 인스턴스를 생성
- now : 시스템 시계로 Temporal 객체를 생성
- of : 주어진 구성요소에서 Temporal 객체의 인스턴스를 생성
- parse : 문자열을 파싱해서 Temporal 객체를 생성
- atOffset : 시간대 오프셋과 Temporal 객체를 합침
- atZone : 시간대와 Temporal 객체를 합침
- format : 지정된 포매터를 이용해서 Temporal 객체를 문자열로 변환 (Instant 는 지원하지 않음)
- get : Temporal 객체의 상태를 읽음
- minus : 특정 시간을 뺀 Temporal 객체의 복사본을 생성
- plus : 특정시간을 더한 Temporal 객체의 복사본을 생성
- with : 일부 상태를 바꾼 Temporal 객체의 복사본을 생성
- TemporalAdjusters
- 날짜 시간 API를 다양한 상황에서 사용할 수 있도록 한 클래스
- LocalDate.of(2014, 3, 18).with(nextOrSame(DayOfWeek.SUNDAY)).with(lastDayOfMonth());
- 2014-03-18 > 2014-03-23 (3/18 포함하여 이후로 처음 나타나는 일요일) > 2014-03-31 (3월의 마지막 일)
- TemporalAdjuster 함수형 인터페이스를 통해 커스텀 기능을 제공할 수 있다.
- TemporalAdjusters 의 팩토리 메서드
- dayOfWeekInMonth : ‘3월의 둘째 화요일’처럼 서수 요일에 해당하는 날짜를 반환하는 TemporalAdjuster를 반환
- firstDayOfMonth : 현재 달의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환
- firstDayOfNextMonth : 다음 달의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환
- firstDayOfNextYear : 내년의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환
- firstDayOfYear : 올해의 첫 번째 날짜를 반환하는 TemporalAdjuster를 반환
- firstInMonth : ‘3월의 첫 번째 화요일’처럼 현재 달의 첫 번째 요일에 해당하는 날짜를 반환하는 TemporalAdjuster를 반환
- lastDayOfMonth : 현재 달의 마지막 날짜를 반환하는 TemporalAdjuster를 반환
- next : 현재 날자 이후로 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환 (현재날짜는 포함하지 않음)
- previous : 현재 날짜 이후로 역으로 날짜를 거슬러 올라가며 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환 (현재날짜 미포함)
- nextOrSame : 현재 날짜 이후로 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환 (현재날짜 포함)
- previousOrSame : 현재 날짜 이후로 역으로 날짜를 거슬러 올라가며 지정한 요일이 처음으로 나타나는 날짜를 반환하는 TemporalAdjuster를 반환(현재날짜 포함)
- DateTimeFormatter
- 기존 DateFormat과 달리 스레드 세이프한 날짜 시간 포매터 클래스
- // 패턴을 통한 포매터 생성
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String formattedDate = LocalDate.of(2014, 3, 18).format(formatter); // 18/03/2014
LocalDate date = LocalDate.parse(formattedDate, formatter);
// 지역화된 DateTimeFormatter
DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
String formattedDateItalian = LocalDate.of(2014, 3, 18).format(italianFormatter); // 18. marzo 2014
LocalDate italianDate = LocalDate.parse(formattedDateItalian, italianFormatter);
// Builder를 통한 Formatter 생성
DateTimeFormatter formatterByBuilder = new DateTimeFormatterBuilder()
.appendText(ChronoField.DAY_OF_MONTH) // d
.appendLiteral(". ").appendText(ChronoField.MONTH_OF_YEAR)
.appendLiteral(" ").appendText(ChronoField.YEAR)
.parseCaseInsensitive()
.toFormatter(Locale.GERMANY);
String formattedDateByBuilder = LocalDate.of(2014, 3, 18).format(formatterByBuilder); // 18. Marz 2014
- ZoneId, ZoneRuls, ZonedDateTime
- TimeZone을 대체하는 불변 클래스로 서머타임(DST) 같은 복잡한 사항이 자동으로 처리 가능
- ZoneRuls 클래스에는 약 40개 정도의 시간대가 있으며 ZoneId의 getRules 메서드를 이용하여 해당 시간대 규정 획득 가능
- ZonedDateTime = LocalDate + LocalTime + ZoneId 로 지정한 시간대에 상대적인 시점을 표현.
- // 기존 TimeZone 클래스에서 ZoneId 변환
ZoneId zoneId = TimeZone.getDefault().toZoneId(); // "Asia/Seoul"
// 지역ID는 "지역/도시" 형식으로 이루어지며 IANA Time Zone Database에서 제공하는 지역 집합정보 사용
ZoneId romeZone = ZoneId.of("Europe/Rome");
// 지정한 시간대에 상대적인 시점을 표현하는 ZonedDateTime
ZonedDateTime zdt1 = LocalDate.of(2014, 3, 18).atStartOfDay(romeZone); // 2014-03-18T00:00+01:00[Europe/Rome]
ZonedDateTime zdt2 = LocalDateTime.of(2014, 3, 18, 13, 45).atZone(romeZone); // 2014-03-18T13:45+01:00[Europe/Rome]
ZonedDateTime zdt3 = Instant.now().atZone(romeZone); // 2016-03-13T11:58:38.743+01:00[Europe/Rome]
// ZoneId를 활용한 LocalDateTime과 Instnat간의 변환 (LocalDateTime은 ZoneId를 상속한 ZoneOffset을 변수로 받는다)
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(Instant.now(), romeZone); // 2016-03-13T11:58:38.743
Instant instantFromDateTime = LocalDateTime.of(2014, 3, 18, 13, 45).toInstant(romeZone.getRules().getOffset(timeFromInstant)); // zoneOffset = "+01:00" (totalSeconds=3600)
// ZonedDateTime은 Instnat, LocalDateTime, LocalDate, LocalTime 모두 변환 가능
Instant instant = zdt1.toInstant(); // 2014-03-17T23:00:00Z
LocalDateTime localDateTime = zdt1.toLocalDateTime(); // 2014-03-18T00:00
- ZoneOffset, OffsetDateTime
- ZoneOffset : ZoneId를 상속한 클래스. 서머타임을 제대로 처리할 수 없으므로 권장하진 않는다
- OffsetDateTime : ISO-8601 캘린더 시스템에서 정의하는 UTC/GMT와 오프셋으로 날짜와 시간 표현
- ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
OffsetDateTime dateTimeInNewYork = OffsetDateTime.of(LocalDateTime.of(2014, 3, 18, 13, 45), newYorkOffset); // 2014-03-18T13:45-05:00
- 대안 캘린더 시스템
- ISO-8601 캘린더 시스템은 실질적으로 전세계에서 통용되나 자바8에서는 추가로 4개의 캘린더 시스템을 제공
- ThaiBuddhistDate :
- MinguoDate :
- JapaneseDate : 일본력 캘린더 시스템
- HijrahDate : 이슬람력 캘린더 시스템
- ChronoLocalDate
- 임의의 연대기에서 특정 날짜를 표현할 수 있는 기능을 제공하는 인터페이스
- 자바8에서 추가된 캘린더시스템은 모두 이 인터페이스를 상속하고있어 LocalDate를 이용해 4개의 클래스 인스턴스로 변환이 가능
- Chronology
- 캘린더 시스템으로 ofLocale을 이용하여 Chronology의 인스턴스 획득이 가능하다.
- JapaneseDate japaneseDate = JapaneseDate.from(LocalDate.of(2014, 3, 18)); // Japanese Heisei 26-03-18
Chronology japaneseChronology = Chronology.ofLocale(Locale.JAPAN);
ChronoLocalDate now = japaneseChronology.dateNow(); // 2016-03-13
'development > Java' 카테고리의 다른 글
[도서] Java8 in Action - Stream (0) | 2017.04.25 |
---|---|
serialize와 serialVersionUID (0) | 2015.06.18 |
Java8 in Action 정리
- 데이터 컬렉션 반복을 선언형으로 처리하고 조립하는 기능
- 멀티 스레드 코드를 구현하지 않고도 데이터 처리과정을 병렬화하여 스레드와 락 걱정없이 처리 가능
- 스트림 API는 매우 비싼 연산이다.
- 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소
- 데이터를 언제 계산하는가
- 스트림 : 요청할 때만 요소를 계산하는 고정된 자료구조 (스트림에 요소를 추가/제거 불가)
- 컬렉션 : 현재 자료구조가 포함하는 모든 값을 메모리에 저장
- 딱 한번만 탐색할 수 있다.
- Awways.asList(“1”,”2”).stream().forEach(System.out::println).forEach(System.out::println)
- 첫번째 forEach에서 데이터가 모두 소비되어 다음 forEach로 전달된 스트림 요소가 존재하지 않음. (파이프라이닝으로 다음 forEach로 전달된 요소가 없음)
- 반복과 병렬성
- 외부반복
- 사용자가 직접 요소를 반복하는 방식 (컬렉션)
- 병렬성을 사용자가 직접 관리해야 함 (syncronized)
- 내부반복
- 반복을 알아서 처리하고 결과 스트림값을 어딘가에 저장
- 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택
- 컬렉션, 배열, I/O 자원 등의 데이터 제공 소스
- 스트림을 생성하면 자료구조의 순서와 같은 순서의 스트림이 생성된다.
- filter, sorted와 같이 다른 연산과 연결될 수 있는 연산
- 중간연산을 이용해서 파이프라인을 구성할 수 있으며 어떤 결과도 생성할 수 있음.
- 게으른 연산 : 중간 연산을 합친 다음에 합쳐진 중간 연산을 최종으로 한번에 처리
- List<String> names =
menu.stream()
.filter(d -> {System.out.println(“filtering” + d.getName()); return d.getCalories() > 300; })
.map(d -> {System.out.println(“mapping” + d.getName()); return d.getname(); })
.limit(3).collect(toList());
System.out.println(names); - filtering pork
mapping pork
filtering beef
mapping beef
filtering chicken
mapping chicken
[pork, beef, chicken] - 쇼트서킷 : limit을 통해 필터링 된 여러 요소 중 3개만 선택됨
- 루프 퓨전 : filter, map 등 다른 연산이지만 한 과정으로 병합되어 처리
- List<String> names =
연산
| 형식
| 사용된 함수 디스크립터
| 설명
|
filter
| Stream<T> filter(Predicate<? super T> predicate)
| T -> boolean
| Predicate(boolean을 반환하는 함수)를 인수로 받아
일치(true)하는 모든 요소를 포함하는 스트림 반환 |
distinct
| Stream<T> distinct()
| 고유 요소 필터링 (상태 있는 언바운드)
hashCode, equals 메서드를 통해 고유여부 판별
|
|
skip
| Stream<T> skip(long n)
| 요소 건너뛰기 (상태 있는 바운드)
처음 n개 요소를 제외한 스트림을 반환
|
|
limit
| Stream<T> limit(long maxSize)
| 스트림 축소 (상태 있는 바운드)
n개 이하의 크기를 갖는 새로운 스트림을 반환
|
|
map
| <R> Stream<R> map(Function<? super T, ? extends R> mapper)
| T -> R
| mapToInt, mapToDouble, mapToLong 메서드를 통해 기본형 특화 스트림(IntStream, DoubleStream, LongStream)으로 변환할 수 있다.
객체스트림으로 복원하려면 숫자 스트림의 boxed 메서드를 호출하면 된다.
|
flatMap
| <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) | T -> Stream<R>
| 각 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑한다.
1개 이상의 스트림의 각 요소를 하나의 스트림으로 변환
|
sorted
| Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
| (T, T) -> int
| 상태 있는 언바운드
|
peek
| Stream<T> peek(Consumer<? super T> action)
| T -> T
| 자신이 확인한 요소를 파이프라인의 다음연산으로 그대로 전달
|
- 스트림 파이프라인을 처리해서 스트림이 아닌 결과를 반환하는 연산
- 보통 최종 연산에 의해 List, Integer, void 등 스트림 이외의 결과가 반환
- 딱 한번만 탐색할 수 있다.
연산
| 형식
| 사용된 함수 디스크립터
| 설명
|
anyMatch
| boolean anyMatch(Predicate<? super T> predicate)
| T -> boolean
| 프레디케이트가 적어도 한 요소와 일치하는지 확인
boolean 반환
|
noneMatch
| boolean noneMatch(Predicate<? super T> predicate)
| T -> boolean
| 프레디케이트가 모든 요소와 일치하는 요소가 없는지 확인
boolean 반환
|
allMatch
| boolean allMatch(Predicate<? super T> predicate)
| T -> boolean
| 프레디케이트가 모든 요소와 일치하는지 확인
boolean 반환
|
findAny
| Optional<T> findAny()
| 스트림에서 임의의 요소를 반환
반환값이 없을경우 NPE를 피하기위해 Optional로 반환
|
|
findFirst
| Optional<T> findFirst()
| ||
forEach
| void forEach(Consumer<? super T> action)
| T -> void
| 스트림의 각 요소를 소비하면서 람다를 적용.
void를 반환한다.
|
collect
| <R, A> R collect(Collector<? super T, A, R> collector)
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner)
| 스트림을 리듀스해서 리스트(toList), 맵(toMap), 정수 형식의 컬렉션을 만든다.
|
|
reduce
| Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner)
| (T, T) -> T
| 상태 있는 바운드(내부 상태의 크기가 한정)
|
count
| long count()
| 스트림의 요소 개수를 반환한다. long을 반환한다
|
- map( w -> w.split(“”)) : “HelloWorld” => String[] {“H”,”e”,”l”,”l”,”o”,”W”,”o”,”r”,”l”,”d”}
- disticnt() : String[] {“H”,”e”,”l”,”o”,”W”,”r”,”d”}
- flatMap(Arrays:stream) : Stream<String[]> => Stream<String>
- Arrays.stream(T[]) : 배열을 스트림으로 변환
- 값의 존재/부재 여부를 표현하는 컨테이너 클래스
- null값 반환으로 인한 에러를 피하기 위해 만들어진 기능.
- 제공 메서드
- isParent() : Optional이 값을 포함하녀 true, 없으면 false
- ifPresent(Consumer<T> block) : 값이 있으면 주어진 블록을 실행
- T get() : 값이 존재하면 값을 반환하고, 없으면 NoSuchElementException을 발생시킴
- T orElse(T other) : 값이 있으면 값을 반환하고, 없으면 기본값(other)을 반환한다.
- 병렬 실행에서는 첫번째 요소를 찾기 어려우므로 요소 반환 순서가 상관없다면 병렬스트림에서는 제약이 적은 findAny를 사용
- int sum = numbers.stream().reduce(0, (a,b) -> a + b);
- numbers의 모든 요소의 합을 구하는 코드
- Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
- 초기값을 받지 않도록 오버로드된 메서드
- 스트림에 아무런 요소가 존재하지 않는경우 초기값으로 할당할 요소가 존재하지 않아 반환값도 존재하게되지 않으므로 Optional을 반환하도록 설계
- 기본형 특화 스트림에서 값이 없는경우와 실제 결과값이 초기값과 같은경우를 구분하기 위해 기본형 특화 Optional 클래스도 제공한다.
- OptionalInt, OptionalDouble, OptionalLong
- 종류 : distinct, skip, limit, sorted, reduce
- 연산을 처리하려면 모든 요소가 버퍼에 추가되어있어야 하거나 결과값을 다음연산의 초기값으로 상태값을 가지고있어야 처리가능한 연산
- 연산을 수행하는데 필요한 저장소의 크기가 정해져 있는경우 바운드(bounded), 스트림의 요소갯수에 따라 무한으로 늘어날 수 있는경우 언바운드(unbounded)
- 값으로 스트림 만들기
- Stream<T> of(T t)
- Stream<T> of(T... values)
- 빈 스트림 만들기
- Stream<T> empty()
- 배열로 스트림 만들기
- Stream<T> stream(T[] array)
- 파일로 스트림 만들기
- Stream<String> lines(Path path, Charset cs)
- 함수로 무한 스트림 만들기
- 요청할 때마다 주어진 함수를 이용해서 무제한으로 값을 생성할 수 있으며, 보통 limit 함수와 함께 사용해서 무한 실행되지 않도록 한다.
- Stream<T> iterate(final T seed, final UnaryOperator<T> f)
- Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println)
- 상태값이 있는 메서드. 연속된 일련의 값을 만들 때 사용
- Stream<T> generate(Supplier<T> s)
- Steam.geneate(Math::random).limit(5).forEach(System.out::println)
- 상태값이 없는 메서드, 연속되지 않은 값을 만들 때 사용
팩토리메서드
| 반환형식
| 사용예제
| 설명
|
toList
| List<T>
| List<Dish> dishes = menuStream.collect(toList());
| 스트림의 모든 항목을 리스트로 수집
|
toSet
| Set<T>
| Set<Dish> dishes = menuStream.collect(toSet());
| 스트림의 모든 항목을 중복없는 집합으로 수집
|
toCollection
| Collection<T>
| Collection<Dish> dishes = menuStream.collect(toCollection(), ArrayList::new);
| 스트림의 모든 항목을
공급자가 제공하는 컬렉션으로 수집
|
counting
| Long
| long howManyDishes = menuStream.collect(counting());
| 스트림의 항목 수 계산
|
summingInt
| Integer
| int totalCalories = menuStream.collect(summingInt(Dish::getCalories));
| 스트림의 항목에서 정수 프로퍼티 값을 더함
|
averagingInt
| Double
| double avgCalories = menuStream.collect(averagingInt(Dish::getCalories));
| 스트림 항목의 정수 프로퍼티의 평균값 계산
|
summarizingInt
| IntSummaryStatistics
| IntSummaryStatistics summary = menuStream.collect(summarizingInt(Dish::getCalories));
// summary = {count=9, sum=4300, min=120, average=477.777778, max = 800} | 스트림 내의 항목의 최대, 최소, 합계, 평균 등의 정수 정보 통계를 수집
|
joining
| String
| String shortMenu = menuStream.map(Dish::getName).collect(joining(“, “));
| 스트림의 각 항목에 toString 메서드를 호출한 결과 문자열을 연결
|
maxBy
| Optional<T>
| Optional<Dish> fattest = menuStream.collect(maxBy(comparingInt(Dish::getCalories)));
| 주어진 비교자를 이용해서 스트림의 최대값 요소를 Optional로 감싼 값을 반환.
스트림에 요소가 없는경우 Optional.empty() 반환
|
minBy
| Optional<T>
| Optional<Dish> lightest = menuStream.collect(minBy(comparingInt(Dish::getCalories)));
| 주어진 비교자를 이요해서 스트림의 최소값 요소를 Optional로 감싼 값을 반환
스트림에 요소가 없는경우 Optional.empty() 반환
|
reducing
| 리듀싱 연산에서 형식을 결정
| int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));
| 누적자를 초깃값으로 설정한 다음 BinaryOperator로 스트림의 각 요소를 반복적으로 누적자와 합쳐 스트림을 하나의 값으로 리듀싱
|
collectingAndThen
| 변환함수가 형식을 반환
| int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size));
| 다른 컬렉터를 감싸고 그 결과에 변환 함수를 적용
|
groupingBy
| Map<K, List<T>>
| Map<Dish.Type, List<Dish>> dishesByType
= menuStream.collect(groupingBy(Dish::getType), toList());
| 하나의 프로퍼티값을 기준으로 스트림의 항목을 그룹화하며 기준 프로퍼티값을 결과 맵의 키로 사용
|
partitioningBy
| Map<Boolean, List<T>>
| Map<Boolean, List<Dish>> vegetarianDishes
= menuStream.collect(partitioningBy(Dish::isVegetarian));
결과 : {false=[pork, beef], true=[french fries, rice, pizza]} | 프레디케이트를 스트림의 각 항목에 적용한 결과로 항목을 분할
|
- reducing
- 도출하려는 결과를 누적하는 컨테이너로 바꾸도록 설계된 메서드
- 가변 컨테이너 작업의 병렬연산에도 안전하다
- reduce
- 두 값을 하나로 도출하는 불변형 연산
- 병렬로 reducing과 같은 연산을 처리하는경우 불변형이 깨지거나 매번 새로운 리스트를 생성하여 값을 할당해야하므로 성능이 저하될 수 있다.
- menu.stream().collect(groupingBy(Dish::getType, groupingBy(dish -> {
if (d.getCalories() <= 400) return CaloricLevel.DIET;
else if (d.getCalorids() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
}))); - 결과
{MEAT={DIET=[chicken], NORMAL=[beef], FAT=[pork]},
FISH={DIET=[prawns], NORMAL=[salmon]},
OTHER={DIET=[rice, seasonal fruit], NORMAL=[french fries, pizza]}}
- 결과
- menu.stream().collect(groupingBy(Dish::getType, counting()));
- 결과 : {MEAT=3, FISH=2, OTHER=4}
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();
enum Characteristics {
CONCURRENT, UNORDERED, IDENTITY_FINISH
}
}
- supplier
- accumulator에서 사용할 빈 누적자 인스턴스를 만드는 함수
- accumulator
- 리듀싱 연산을 수행하는 함수(void return) 반환
- supplier가 생성한 누적자 인스턴스를 받아 함수의 상태값을 저장하고 반환값은 void가 된다. (내부탐색만 가능하며 계속 상태가 바뀌므로 어떤값일지 추적 불가)
- combiner
- 마지막으로 리듀싱 연산에서 사용할 함수를 반환
- 스트림의 서로 다른 서브파트를 병렬로 처리할 때 누적자가 이 결과를 어떻게 처리할지 정의
- 병렬 처리시 스트림을 분할해야 하는지 정의하는 조건이 거짓으로 바뀌기 전까지 원래 스트림을 재귀적으로 분할하며,
일반적으로 프로세싱 코어의 개수 이하의 병렬작업이 효율적이다.(p214)
- finisher
- 누적과정을 끝낼 때 호출할 함수를 반환
- 누적자 객체가 이미 최종결과인 경우 변환과정이 필요없는 항등함수(Function.identity())를 반환하면 된다.
- characteristics
- 스트림을 병렬로 리듀스할 것인지, 병렬로 리듀스 할 경우 어떤 최적화를 선택해야할지 힌트를 제공
- UNORDERED
- 리듀싱 결과는 스트림 요소의 방문순서나 누적 순서에 영향을 받지 않는다.
- CONCURRENT
- 다중 스레드에서 accumulator 함수를 동시에 호출할 수 있으며 이 컬렉터는 스트림의 병렬 리듀싱을 수행할 수 있다.
- UNORDERED를 함께 설정하지 않았다면 데이터 소스의 순서가 무의미한 상황에서만 병렬 리듀싱을 수행할 수 있다.
- IDENTITY_FINISH
- 리듀싱 과정의 최종 결과로 누적자 객체를 바로 사용할 수 있으며, 누적자 객체를 A -> R로 안전하게 형변환 할 수 있다.
- 스트림 자체에는 아무 변화도 일어나지 않는다
- parallel을 호출하면 내부적으로 이후 연산이 병렬로 수행해야 함을 의미하는 불린 플래그가 설정된다.
- 병렬 스트림을 순차 스트림으로 바꿀 수 있다.
- sequential을 호출하면 이후 연산이 순차적으로 수행해야 함을 의미하는 불린 플래그가 설정된다.
- 병렬스트림은 내부적으로 ForkJoinPool을 사용
- ForkJoinPool은 기본적으로 Runtime.getRuntime().availableProcessors()가 반환하는 값에 상응하는 스레드를 사용(가상 프로세서를 포함한 프로세서 수)
- 자료구조 변화에 따른 auto boxing으로 인한 성능저하
- 기본형 특화 스트림을 사용하여 auto boxing 회피
- 병렬실행을 위한 재귀적인 청크 분할, 스레드 할당, 머지 등 작업을 위한 비용과 성능개선비용의 관계
- 성능측정을 통한 병렬 스트림 사용시의 이점 확인
- 소량의 데이터의 경우 병렬화 과정으로 인한 비용이 더 크게 발생할 수 있다.
- 최종연산의 병합과정 비용보다 병렬 스트림으로 얻은 성능의 이익이 더 큰지 확인
- 순서에 의존하는 limit, findFirst 등 연산은 병렬스트림에서 성능이 저하됨.
- 공유된 가변상태 변수로 인한 로직 오류 발생
- 효율적으로 분할 가능한 자료구조로 스트림이 구성되었는지 확인
- 분해성
- 훌륭함 : ArrayList, IntStream.range
- 좋음 : HashSet, TreeSet
- 나쁨 : LinkedList, Stream.iterate
- 작업 훔치기
- ForkJoinPool에서 각 스레드는 자신에게 할당된 태스크를 포함하는 이중연결 리스트를 참조하면서 작업이 끝날때 마다 큐의 HEAD 에서 다른 태스크를 가져와 작업을 처리
- 특정 스레드가 먼저 작업이 완료되었을 ㄷ경우 다른 스레드 큐의 TAIL 에서 작업을 훔쳐와 모든 큐가 빌 때 까지 이 작업을 반복하여 스레드간 작업부하를 비슷한 수준으로 유지
- Spliterator
- 자바 8은 컬렉션 프레임워크에 포함된 모든 자료구조에 대해 사용할 수 있는 디폴트 Spliterator 구현을 제공
- 제공 메서드
- tryAdvance : 일반적인 Iterator 동작과 동일
- trySplit : 스트림을 재귀적으로 분할하며 모든 trySplit 결과가 null이면 분할과정이 종료된다.
- Spliterator의 일부 요소(자신이 반환한 요소)를 분할해서 두번째 Spliterator를 생성하는 메서드
- estimateSize 메서드로 탐색해야할 요소 수 정보를 제공할 수 있다.(정확성 X)
- characteristics : Spliterator 자체의 특성 집합을 포함하는 int를 반환하며, 이 특성을 이용해서 Spliterator를 더 잘 제어하고 최적화 할 수 있다
- ORDERED, DISTINCT, SORTED, SIZE, NONNULL, IMMUTABLE, CONCURRENT, SUBSIZED
'development > Java' 카테고리의 다른 글
[도서] Java8 in Action - 새로운 날짜/시간 API (0) | 2017.04.25 |
---|---|
serialize와 serialVersionUID (0) | 2015.06.18 |
- 참고 URL
- JCA로 이해하는 암호화와 보안 : http://d2.naver.com/helloworld/197937
- JCA로 이해하는 암호화와 보안2 : http://d2.naver.com/helloworld/227016
- 안전한 패스워드 저장 : http://d2.naver.com/helloworld/318732
- 암호화 알고리즘 종류와 관련 용어 : http://modoris.blogspot.kr/2011/11/blog-post_08.html
- [KISA] 암호기술 구현 안내서 2013
- http://ohgyun.com/433
- http://dinguri.tistory.com/entry/Base643216-Encodings
- update, doFinal 메서드 차이
- wikipedia
- https://en.wikipedia.org/wiki/Cryptographic_hash_function
- https://en.wikipedia.org/wiki/Hash-based_message_authentication_code
- https://ko.wikipedia.org/wiki/%EB%B2%A0%EC%9D%B4%EC%8A%A464
- bcrypt cryptography
개념은 알지만 막상 구현하려니 헷갈리던 암호화,, Java에서는 다행히도 javax.crypto 패키지 하위의 클래스를 통해 보안 관련 기능을 제공하고있다.
위의 URL들은 암호화/복호화의 개념을 잡을수 있도록 잘 정리된 페이지이며, 이를 바탕으로 나중에 참고하기 위해 간단히 정리해보고자 한다 :3
나름대로 정리한 자료지만 혹시 틀릴수도 있으니 참고 URL이나 미심쩍을때는 최신 정보를 검색해보는것을 추천함.
MesasgeDigest
MD5, SHA1과 같은 알고리즘이 MessageDigest(해시)의 대표적인 알고리즘이다.
다른말로는 체크섬(checksum)이라는 명칭으로도 불리며, 원본파일(메시지)가 그대로인지 파악하는 무결성 검사에 사용한다.
이 알고리즘들은 다양한 길이의 입력값을 고정길이의 해시값으로 출력하며, 단방향 알고리즘이므로 해시값에서 역으로 원본값을 추출할 수 없다.
때문에 패스워드를 암호화 할 때, 원본 패스워드의 노출을 막기 위해 해시값으로 변환하여 저장하고, 마찬가지로 비밀번호 검증시에도 해시화된 값이 동일한지로 비교하게된다.
[대표적인 해쉬함수] (출처 : KISA)
| HAS-160 | SHA-1 | SHA-2 | |||
SHA-224 | SHA-256 | SHA-384 | SHA-512 | |||
해시값 길이(bit) | 160 | 160 | 224 | 256 | 384 | 512 |
[보안강도별 해쉬함수 분류] (출처 : KISA)
보안강도(*) |
해쉬함수 |
권고여부 |
80비트 미만 |
MD5, SHA-1 |
권고하지 않음 |
80비트 | HAS-160 | |
112비트 | SHA-224 | 권고함 |
128비트 | SHA-256 | |
192비트 | SHA-384 | |
256비트 | SHA-512 |
* 보안강도 : 2^n번의 계산을 해야 비밀키 또는 알고리즘 취약성을 알아낼 수 있다는 것을 의미한다. (국내 권고수준은 112비트 이상)
비밀번호와 Salt
동일한 비밀번호이더라도 다른 해쉬결과를 얻기 위해 Salt라는 비밀값을 추가하여 해쉬함수를 적용한다.
사용자별로 다른 Salt값을 적용하면 비밀번호만 해싱하는것에 비해 비밀번호 사전공격 취약점을 해결하고, 동일비밀번호이더라도 다른 해쉬결과를 얻을 수 있어 암호를 유추하기 어려워진다.
salt를 추가하는 방법은 다음과 같이 다양한 방식이 존재한다.
비밀번호와 salt를 연접하는 방법은 가낭 흔히 사용하므로 다양한 방법을 응용하여 사용하는것이 좋다.
- hashing ( salt || password || salt)
- hashing (salt || hashing (password))
- hashing ( password || hashing (password || salt))
- hashing ( hashing (salt) || hashing (password))
- hashing1 (hashing2 (password || salt))
MessageDigest와 Cryptography
내가 헷갈린다는 것도 몰랐던 Digest와 암호화의 차이.. 사실 이미 구현된 결과만 놓고보면.. 둘다 알 수 없는 숫자 문자열의 나열들이니..
변명이지만 변명할 수도 있겠지. :3 나말고도 헷갈리는사람이 꽤 있는듯하다.
이 둘의 차이점은 복호화가 가능한지 여부인것으로 보인다. MessageDigest는 단방향 알고리즘으로 결과값에서 원본값을 추출할 수 없으나 Cryptography는 암호화 키(대칭키, 비대칭키)를 통해 결과값에서 원본값으로 복원을 할 수 있다.
MAC (Message Authentication Codes)
암호화관련 자료를 찾아보면서 낯선것은 Mac 이었다. 처음 봤을때는 A사의 사과제품을 떠올렸던.. (그..그럴수도 있지!!)
MAC은 초기화 비밀키(SecretKey, 대칭키)를 활용하여 해시값을 추출하며 MessageDigest와 유사하지만 다르다.
이 초기화 키로 인해 MessageDigest는 누구나 무결성 검사가 가능하지만(예: 파일다운로드시 checksum 활용), MAC은 초기화 키를 가지고 있어야만 동일한 해시값을 추출하여 무결성 검사가 가능하다.
대표적인 알고리즘으로는 HMAC이 있다.
[MAC 동작 흐름(이미지 출처) ]
Base64Encoding/Decoding
Base64는 8비트 이진 데이터를 문자코드에 영향받지 않는 공통 ASCII 영역 문자열로 바꾸는 인코딩 방식이다.
인코딩된 문자열은 알파벳 대소문자와 숫자, +, / 기호 64개로 이루어지며 =는 끝을 알리는 코드로 사용하며, 인코딩 된 문자열은 원본보다 약 4/3 정도 증가한다.
Cryptography
해시함수와 다르게 Cryptography는 암호화, 복호화가 가능한 양방향 알고리즘을 사용한다.
알고리즘에는 암호화를 위한 키가 필요하며, 키의 유형에 따라 대칭키(공유키), 비대칭키(공개키, 비밀키)로 나뉜다.
[암호화 알고리즘 분류] (출처 : 모도리 블로그)
알고리즘 분류 | 대표 알고리즘 | |
대칭형 | 블록 암호 | DES (Data Encryption Standard), 3-DES |
스트림 암호 | MASK | |
비대칭형 | RSA (Rivest Shamir Adleman) |
[대표적 블록암호(CBC) 알고리즘] (출처 : KISA)
|
SEED |
ARIA |
AES |
입출력크기(비트) |
128 |
128 |
128 |
비밀키크기(비트) |
128 |
128 / 192 / 256 |
128 / 192 / 256 |
[보안강도별 블록암호 알고리즘 분류] (출처 : KISA)
보안강도 |
블록암호 알고리즘 |
권고여부 |
80 비트 미만 |
DES |
권고하지 않음 |
80 비트 |
2TDEA |
|
112 비트 |
3TDEA |
|
128 비트 |
SEED, HIGHT, ARIA-128, AES-128 |
권고함 |
192 비트 |
ARIA-192, AES-192 |
|
256 비트 |
ARIA-256, AES-256 |
JCA (Java Cryptography Architecture)
Java에서는 JCA (Java Cryptography Architecture)라는 보안 플랫폼을 통해 해시, 암호화, 전자서명 등 다양한 보안기능을 제공하고있다.
Cipher
Cipher 클래스는 암호화/복호화 기능을 제공한다.
예) Cipher cipher = Cipher.getInstance("AES");
알고리즘명, 피드백모드, 패딩모드를 전달하여 생성된 Cipher 객체를 반환받는다. (피드백모드, 패딩모드 생략 가능)
내부적으로는 전달된 알고리즘이 구현한 서비스객체와 피드백모드(default:ECB), 패딩모드(default : PKCS5Padding)를 설정하여 Cipher 객체를 생성 후 반환한다.
예) cipher.init(Cipher.ENCRYPT_MODE, key);
이 Cipher 객체를 사용하기 위해서 init 메서드를 통해 운영모드와 Key값을 전달하여 초기화한다.
init 메서드는 인자로 opmode, key, (AlgorithmParameters)params, random을 전달받는데 운영모드에 따라 생략이 가능하며 생략된 값은 내부적으로 자동 생성하여 사용한다. 기존에 설정된 변수값은 모두 삭제되므로 cipher객체를 초기화하여 사용하는경우, getParameters() 메서드를 호출하여 암호화시 자동으로 생성된 params값을 저장해두어야 한다. 이 값은 복호화시에 동일하게 전달되어야 복호화가 가능하기 때문이다.
- Cipher 운영모드(opmode)
- ENCRYPT_MODE : 암호화 모드.
- DECRYPT_MODE : 복호화 모드
- WRAP_MODE : key를 안전하게 전송할 수 있도록 java.security.Key를 래핑하여 바이트 변환
- UNWRAP_MODE : WRAP_MODE에서 래핑한 Key 를 java.security.Key 객체로 변환
암호화/복호화는 update(), doFinal() 메서드를 통해 수행하며 마지막 데이터(혹은 단일 바이트 데이터)인경우 doFinal을 통해 해당 작업이 완료되었음을 알린다. doFinal이 호출되면 cipher 객체는 모든 데이터에 대한 암호화가 완료되었음을 인지하고 데이터블록의 빈자리에 Padding작업을 수행한다.
[ Cipher 객체 동작 흐름(이미지 출처) ]
블록암호 피드백모드와 IV (Initial Value)
블록 암호화는 블록단위로 데이터를 암호화 하는데 입력데이터가 같으면 동일한 암호화된 결과값을 갖게되어 해킹공격에 취약점을 가지게된다. 이러한 점을 보완하고 암호화의 복잡도를 높이기 위해 피드백 모드가 도입되었다.
피드백모드는 이전 데이터블록을 활용해 다음 블록의 암호화를 진행한다. N번째 암호화 과정에서 N번째 입력 데이터 블록 (또는 N번째 암호화된 결과 데이터 블록)과 N-1번째 입력 데이터 블록(또는 N-1번째 암호화된 결과 데이터 블록)을 XOR 연산으로 조합하는 것이다. 이때 첫번째 블록의 경우는 N-1번째 블록이 존재하지 않기때문에 이 값을 대체하는 변수값이 IV(Initial Value) 이다. IV값을 통해 동일한 입력값이더라도 전혀다른 암호화된 결과값을 얻을 수 있다.
다음의 그림은 원본파일 / 피드백모드를 사용하지 않는 암호화 결과 / 피드백모드를 사용한 암호화 결과의 차이를 잘 나타내고 있다.
[ 이미지 암호화(이미지 출처: http://en.wikipedia.org/wiki/Modes_of_operation) ]
Simple Implements Examples (for JAVA)
Encode/Decode Codec Library Import
※ JVM의 encode, decode library를 사용하지 않는 이유
- http://www.oracle.com/technetwork/java/faq-sun-packages-142232.html
- java.*, javax.*, org.* 패키지에서 제공되는 클래스는 JVM에서 public API를 제공하고 관리되지만,
sun.misc.*, com.sun.* 패키지의 기능은 그렇지 않으므로 언제든 변경되거나 사라질 수 있는 위험성을 내포하고 있다.
<!-- http://mvnrepository.com/artifact/commons-codec/commons-codec/1.9 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
Byte Array <-> Hex String
import org.apache.commons.codec.binary.Hex;
Hex.encodeHexString(byteArray); // byte[] => hex string
Hex.decode(hexString); // hex string => byte[]
BASE64 Encoding/Decoding
import org.apache.commons.codec.binary.Base64;
Base64.encodeBase64String(byteArray); // byte[] => base64 encoded string
Base64.decodeBase64(encodedString); // base64 encoded string => byte[] , new String(byteArray)를 통해 문자열로 변경
AES 암호화 (출처 : AES256 암호화 Java 샘플)
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AES256Cipher {
private final String AES_ALGORITHM = "AES/CBC/PKCS5Padding";
/** @return encryptData */
public static byte[] encode(String key, String plainData, String iv) {
SecretKey secretKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv.getBytes()));
return cipher.doFinal(plainData.getBytes("UTF-8"));
}
/** @return decryptData(plainData) */
public static String decode(String key, String encryptData, String iv) {
SecretKey secretKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv.getBytes()));
return new String(cipher.doFinal(encryptData.getBytes(), "UTF-8");
}
}
RSA 암호화
import javax.crypto.Cipher;
import java.security.Key;
public class RSACipher {
private final String RSA_ALGORITHM = "RSA";
private final int KEY_SIZE = 2048;
/** @return KeyPair */
public static KeyPair generateRSAKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
generator.initialize(KEY_SIZE);
return generator.genKeyPair();
}
/** @return encryptData */
public static byte[] encode(Key key, String plainData) {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(plainData.getBytes("UTF-8"));
}
/** @return decryptData(plainData) */
public static String decode(Key key, String encryptData, String iv) {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv.getBytes()));
return new String(cipher.doFinal(encryptData.getBytes(), "UTF-8");
}
public static void main(String[] args) {
String data = "원본 데이터입니다.";
KeyPair rsaKeyPair = generateRSAKeyPair();
byte[] encrypted = encode(rsaKeyPair.getPrivate(), data);
String decrypted = decode(rsaKeyPair.getPublic(), encrypted);
assertEquals(data, decrypted);
}
}
'기타' 카테고리의 다른 글
[도서] 가장 쉬운 데이터베이스 설계 책 (0) | 2014.11.25 |
---|---|
[도서] 오라클의 눈으로 알티베이스를 보다 (0) | 2014.10.16 |
http://cafe.naver.com/swnara/369
http://kevinx64.net/107
개발하다보면 종종 serialVersionUID를 보게된다. 이값은 어디에 사용하는것일까 궁금해서 찾아보게 되었다.
Serialize (직렬화)
객체를 파일로 저장하거나, 혹은 다른 사람에서 전송하기 위해서는 직렬화(serialize) 및 역직렬화(deserialize)가 필요하다.
직렬화란 메모리나 저장공간에 담을 수 있도록 일렬로 만드는 것을 의미하며, 직렬화를 해야만 객체를 저장하거나 통신할 수 있다.
Serializable 인터페이스는 구현 메소드, 필드가 없는 껍데기 인터페이스로 JAVA에서는 이 인터페이스를 구현하여 직렬화가 가능한 객체임을 인식한다.
Serializable을 구현한 객체를 저장하면 해당 class에 정의된 private을 포함한 모든 필드와 값, super class의 모든 필드 및 값이 저장된다.
SerialVersionUID
Serializable 인터페이스를 구현하게되면 IDE에서 serialVersionUID를 생성하도록 warning을 표시한다. 이 SerialVersionUID는 무엇에 쓰이는것일까? 그리고 해당 값을 선언하지 않으면 어떻게 되는것일까?
http://kevinx64.net/107 에서 이 의문에 대한 답을 찾을 수 있었다.
직렬화 가능(Serializable) 클래스가 serialVersionUID를 명시적으로 선언하지 않는 경우, 직렬화 런타임은 「Java(TM) 객체 직렬화 스펙」에서 설명하고 있듯이, 클래스의 다양한 측면에 근거해, 클래스의 serialVersionUID의 Default 값를 계산한다. 다만, 모든 직렬화 가능 클래스가 serialVersionUID를 명시적으로 선언하는 것을 강하게 추천한다. Default의 serialVersionUID 계산이, 컴파일러의 구현에 따라서, 다를 가능성이 있는 클래스의 영향을 받기 쉽고, 직렬화 복원 중에 예기치 않은 InvalidClassException 가 발생할 가능성이 있기 때문이다. 따라서, java 컴파일러의 구현이 달라도 serialVersionUID의 일관성을 확보로 하려면, 직렬화 가능 클래스가 serialVersionUID를 명시적으로 선언하지 않으면 안된다. 또, serialVersionUID 의 명시적인 선언에서는 private 수식자를 사용하는 것을 적극 추천한다. 이러한 선언은 직접적으로 선언하는 클래스에게만 적용되게 하기 위한 것이다. private으로 선언하면, serialVersionUID 필드를 상속되는 멤버와 같이 사용하지 않는다.
SerialVersionUID는 객체 직렬화 시에 동일한 Class 명을 갖고있더라도 실제로 동일한 class가 맞는지 검사하기 위해 version을 지정하는것이며, 이 값을 명시적으로 지정하지 않으면 JVM은 default로 serialVersionUID를 계산하여 사용한다.
여러가지 근거를 통해 자동으로 serialVersionUID를 계산해 낼 때 환경적인 사유 혹은 불특정한 이유로 인해 이 값이 다르게 계산될 수도 있으며, 이 경우 InvalidClassException이 발생할 수 있으므로 명시적으로 선언해주는것을 권장한다.
Transient
직렬화 시에 대상에서 제외하고 싶은 경우 변수 선언 앞에 transient를 선언하면 직렬화 대상에서 제외가 된다.
'development > Java' 카테고리의 다른 글
[도서] Java8 in Action - 새로운 날짜/시간 API (0) | 2017.04.25 |
---|---|
[도서] Java8 in Action - Stream (0) | 2017.04.25 |
- 참고 URL
- https://code.google.com/p/powermock/wiki/MockitoUsage13
- http://stackoverflow.com/questions/11458963/mockito-0-matchers-expected-1-recorded-invaliduseofmatchersexception
단위테스트는 환경에 영향받지 않고 어떤 환경이든 해당 단위(메소드 또는 기능)에 대해 의도한 대로 로직이 구현되어있음을 확인하는것으로
1. library 준비
- http://mvnrepository.com/artifact/org.powermock
- powermock-module-junit : junit과 연동하기위한 dependency. Runner class가 들어있다.
- powermock-api-mockito : Mock 처리를 위한 dependency. Powermockito class가 들어있다.
@RunWith(PowerMockRunner.class) |
FINAL METHOD Mocking
- 참고 URL
- http://stackoverflow.com/questions/11458963/mockito-0-matchers-expected-1-recorded-invaliduseofmatchersexception
- http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/transaction/support/AbstractPlatformTransactionManager.html#getTransaction(org.springframework.transaction.TransactionDefinition)
- http://docs.spring.io/spring-framework/docs/2.0.x/api/org/springframework/transaction/PlatformTransactionManager.html#getTransaction(org.springframework.transaction.TransactionDefinition)
[Logic] - 메소드 내에 다음과 같은 로직이 들어있었다. 바꾸기는 부담스럽고(history를 알 수 없어서), 일단 테스트를 하려고보니... @Autowired // 사실은 spring version이 낮아 setter method 로 들어갔음. DefaultTransactionDefinition def = new DefaultTransactionDefinition(); |
[TEST] - transactionManager.getTransaction(def) 이 statement를 mocking 하기위해 다음과 같이 했더니 에러가 발생한다. when(mockTransactionManager.getTransaction(any(DefaultTransactionDefinition.class))).thenReturn(mockTransactionStatus); org.mockito.exceptions.misusing.InvalidUseOfMatchersException: |
뭐가 문제인지 몰라 한참을 확인해보니.... 구현체의 메소드가 final로 선언되어 변경이 불가능한것이었다! 그래서..저런에러가..
그래서인지 실제 로직에서는 인터페이스를 통해 구현체를 주입받도록 권고하나 보다.. 이 TransactionManager의 interface인 PlatformTransactionManager는 getTransaction 메서드가 public으로 선언되어있어 일반적인 방법으로도 충분히 mocking이 가능하다. |
'myplace' 카테고리의 다른 글
Maven test와 junit의 동작 차이 (@Ignore annotation이 다르게 동작해요) (0) | 2015.05.14 |
---|---|
SonarQube (0) | 2014.09.06 |
jenkins CI Server (0) | 2014.09.05 |
- 참고 URL :
- http://junit.10954.n7.nabble.com/maven2-with-Junit-4-ignores-Test-annotations-td8629.html
- http://maven.apache.org/surefire/maven-surefire-plugin/examples/junit.html
- http://stackoverflow.com/questions/7535177/if-i-ignore-a-test-class-in-junit4-does-beforeclass-still-run
IDE에서 Junit4로 테스트시 @Ignore 등 annotation 정상동작함.
mvn clean compile test 명령어를 통해 Maven으로 test 수행시 @Ignore 등 annotation이 무시되어 Ignore처리된 테스트도 수행됨(결과는 failure로 잡힘)
원인은 못찾음. 뭔가 동일한 테스트 클래스가 존재한다는 말은 있지만..
해결방법은 @Ignore 처리한 Class를 maven 설정에서 exclude에 추가.
<build>설정에 다음내용 추가
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.8</version> <configuration> <includes> <include>**/*Test.java</include> </includes> <excludes> <exclude>**/*DAOTest.java</exclude> </excludes> </configuration> <dependencies> <dependency> <groupId>org.apache.maven.surefire</groupId> <!-- Use the older JUnit 4 provider --> <artifactId>surefire-junit4</artifactId> <version>2.8</version> </dependency> </dependencies> </plugin> |
'myplace' 카테고리의 다른 글
Static, Final Method Unit Test (0) | 2015.05.14 |
---|---|
SonarQube (0) | 2014.09.06 |
jenkins CI Server (0) | 2014.09.05 |
처리 성능 개선
데이터베이스 규칙 완화 또는 파기에 따른 문제점과 성능개선에 대한 가치 비교가 필요하다
데이터 구조의 무분별한 수정의 결과로 발생할 수 있는 문제들
- 일관성 없는 데이터 : 불필요한 이중 필드를 도입한 경우 발생
- 중복 데이터 : 불필요한 이중 필드를 도입한 경우 발생
- 손상된 데이터 무결성
- 부정확한 정보 제공
- 하드웨어 Scale Up / Scale Out
- OS 옵션 최적화
- 데이터베이스 구조가 올바르게 설계되었는지 검토
- 데이터베이스의 구현 검토 : 효율적이고 안전하게 정의했는가 검토
- 데이터베이스를 사용하는 응용프로그램 검토
설계 지침
후보 키의 요소
- 다중 부분 필드가 될 수 없다.
- 유일한 값을 가져아 한다.
- 널(null)값을 포함할 수 없다.
- 그 값은 조직의 보안이나 비밀 규칙을 깨뜨리도록 할 수 없다.
- 그 값이 전체 또는 부분적으로 선택적이지 않다.
- 유일성을 정의하기 위해 필요한 최소 개수의 필드들을 포함한다.
- 그 값들이 테이블 내의 각 레코드들을 유일하게 그리고 배타적으로 식별해야만 한다.
- 그 값이 주어진 레코드 내의 각 필드의 값을 구체적으로 식별해야만 한다.
- 그 값은 아주 드물고 극단적인 경우에만 수정될 수 있다.
- 복사된 주 키의 이름과 같은 이름을 갖는다.
- 복사된 기본 키에 대한 필드 명세의 복제를 사용한다.
- 참조하는 주 키에서 값을 도출한다.
- 다중 부분 필드일 수 없다.
- 반드시 유일한 값을 가져야 한다.
- 널(null)값을 포함할 수 없다.
- 그 값이 조직의 보안 또는 비밀 규칙을 깨뜨릴 수 없다.
- 그 값이 전체 또는 부분적으로 선택적이지 않다.
- 유일성을 정의하기 위해 필요한 최소 개수의 필드들을 포함한다.
- 그 값이 테이블에 있는 각 레코드들을 유일하고 배타적으로 식별해야만 한다.
- 그 값은 주어진 레코드에 있는 각 필드값을 구체적으로 식별해야 한다.
- 그 값은 아주 드물고 극단적인 경우에만 수정될 수 있다.
- 테이블의 대상의 뚜렷한 특징을 나타낸다.
- 단 하나의 값만을 포함한다.
- 더 작은 구성요소로 해체될 수 없다.
- 계산되거나 연결된 값을 포함하지 않는다.
- 전체 데이터베이스 구조 내에서 유일하다.
- 하나 이상의 테이블에 나타날 때에는 속성들의 대부분을 보유한다.
- 개체 또는 사건일 수 있는 하나의 대상을 나타낸다.
- 주 키를 갖는다.
- 다중 부분 필드나 다중값 필드를 포함하지 않는다.
- 계산된 필드를 포함하지 않는다.
- 불필요한 중복 필드들을 포함하지 않는다.
- 절대적으로 최소화된 중복 데이터만을 포함한다.
- 전체 조직에 의미 있는 유일하고 설명적인 이름을 생성한다.
- 필드가 나타내는 특징들을 정확하고 명확하게 그리고 모호하지 않게 식별하는 이름을 생성한다.
- 필드가 나타내는 특징의 의미를 전달할 때 필요한 최소 개수의 단어를 사용한다.
- 두문자어를 사용하지 않으며, 약어를 신중하게 사용한다.
- 필드 이름의 의미를 혼동할 수 있는 단어를 사용하지 않는다.
- 암시적이거나 명시적으로 하나 이상의 특징을 식별하는 이름을 사용하지 않는다.
- 이름의 단수형을 사용한다.
- 전체 조직에 의미 있는 유일하고 설명적인 이름을 생성한다.
- 테이블의 대상을 정확하고 명확하게 그리고 모호하지 않게 식별하는 이름을 생성한다.
- 테이블의 대상을 전달할 때 필요한 최소 개수의 단어를 사용한다.
- 물리적인 특징을 전달하는 단어들을 사용하지 않는다.
- 두문자어와 약어를 사용하지 않는다.
- 테이블에 입력될 수 있는 데이터를 과도하게 제한하는 이름이나 다른 단어들을 사용하지 않는다.
- 하나 이상의 대상을 암시적이거나 명시적으로 식별하는 이름을 사용하지 않는다.
- 이름의 복수형을 사용한다.
'기타' 카테고리의 다른 글
암호화, 해시 그리고 인코딩 (0) | 2015.07.28 |
---|---|
[도서] 오라클의 눈으로 알티베이스를 보다 (0) | 2014.10.16 |
레시피 URL : http://amyzzung.tistory.com/m/post/74
1.재료준비(3장나오는양)
▣ 주재료 : 쪽파 1/2단, 오징어1마리,중새우 15마리, 부침가루 2종이컵, 물2종이컵
* 부침 마지막단계에 달걀물 부어 부쳐주세요. 저는 알러지가 심할때라서 이번만 생략했어요.
▣ 양념재료(밥숟가락) : 새우가루3, 소금은 기호에 따라 추가/생략.
*반죽에 곱게 갈은 새우가루를 넣으면 더욱 맛있습니다.
2.재료손질
▣ 쪽파 : 깨끗히 씻은 쪽파는 후라이팬 크기에 맞게 1/2로 잘라주기 (길이가 후라이팬보다 길 경우만 잘라주세요.)
▣ 오징어 : 껍질을 벗긴 오징어는 앏게 썰어주기 (대략 가로 3cm x 세로 0.7cm)
▣ 새우 : 굵게 채썰어주기 (대략 강남콩크기)
3.요리만들기
▣ 첫째 - 양푼에 부침가루1: 물1 비율로 반죽을 만들고 새우가루를 골고루 섞어주세요.
*일반 부침개보다 물을 더 많이 넣어 걸쭉하게 해서 얇게 부쳐야 맛있어요. 물이 적은 반죽은 쪽파 사이사이 파고들어가지 못하고 두껍고 뭉칩니다.
▣ 둘째 - 파에 반죽에 잘 붙을 수 있도록 부침가루를 뿌려주세요. 구멍이 큰 양념통에 넣고 뿌려주시면 편리합니다.
▣ 셋째 - 부치기 직전에 새우,오징어등 해물을 넣고 반죽과 섞어주세요.
* 미리 반죽에 해물을 넣어 섞어놓으면 부침개를 뒤집을때 해물만 이탈되는 참사(?)를 좀 방지할 수 있어요.
▣ 넷째 - 약불에 달구어진 후라이팬에 식용유를 충분히 두르고 파를 올려주세요.
*센불에는 파가 타구요. 식용유를 충분히 넣어 부쳐야 맛있어요.
*파를 반죽에 미리 넣거나 반죽부친후 그위에 파를 올리지 않고.. 파를 가장 먼저 후라이팬에 올리는 이유는 구운 파의 풍미를 살리고자입니다. (제 입맛에 맞춘 개인의견이예요)
▣ 다섯째 - 후라이팬에 파를 올리자마자 해물반죽을 골고루 뿌려서 앞뒤로 노릇하게 구워주세요.
'hobby > Food' 카테고리의 다른 글
친구가 알려준 '제육볶음' (0) | 2014.10.28 |
---|