아래와 같은 toMap 코드를 사용하면 NPE 에러가 발핸다.


    final Map<String, Object> valueMap = pingFailedHosts

        .stream()

        .collect(Collectors.toMap(c->c, null));

        


toMap 메소드는 KeyMapper와 ValueMapper를 전달하는 형태인데. 

public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}


toMap 원형 함수를 보면, mergeFunction , mapSupplier가 추가된다. 

public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}


https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html



toMap 메소드의 매개변수를 보면, mergeFunction이 Map.merge()와 연관되어 있음을 알 수 있다. 

Parameters:
keyMapper - a mapping function to produce keys
valueMapper - a mapping function to produce values
mergeFunction - a merge function, used to resolve collisions between values associated with the same key, as supplied to Map.merge(Object, Object, BiFunction)
mapSupplier - a function which returns a new, empty Map into which the results will be inserted







코드를 따라가 들어가면


private static <K, V, M extends Map<K,V>>
BinaryOperator<M> mapMerger(BinaryOperator<V> mergeFunction) {
return (m1, m2) -> {
for (Map.Entry<K,V> e : m2.entrySet())
m1.merge(e.getKey(), e.getValue(), mergeFunction);
return m1;
};
}


m1.merge() 메소드가 바로 Map.merge() 메소드이다.


여기 코드 보면, value 값이 not null이어야 한다. 


default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value)
;
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}


코드의 주석이나, 오라클 javadoc 문서를 보면 역시 NPE를 발행하는 코드임을 확인할 수 있다.


NullPointerException - if the specified key is null and this map does not support null keys or the value or remappingFunction is null





따라서 key나 value의 값이 null일 확률이 높다면, toMap()를 사용하지 말아야 한다.


아래와 같이 naive하게 개발하는 것이 좋다.


    final Map<String, String> valueMap2 = Arrays.asList("cacti.google.com", "sun.google.com")

        .stream()

        .collect(HashMap::new, (m,v)->m.put(v, null), HashMap::putAll);




Posted by 김용환 '김용환'




sudo -i

add-apt-repository ppa:linuxuprising/java

apt-get update

apt-get install oracle-java10-installer


proxy 이슈가 있다면 setproxy를 써서 설치한다.



Posted by 김용환 '김용환'


종종 자바 오브젝트가 딱 데이터만큼 메모리를 소비한다고 생각하는 개발자들이 있어서 공유한다. 메타데이터 데이터가 자바 오브젝트에 있다. 사실 String 이나 Java collection 코드를 보면 생각보다 데이터를 많이 갖고 있음을 알면 좋다. 




자바 오브젝트는 접근하기에 충분히 빠르지만 원시 필드의 실제(원시) 데이터보다 2~5배 더 많은 공간을 차지한다. 



예를 들어 각 개별 자바 오브젝트에는 오브젝트 헤더를 갖고 있는데 16바이트를 포함한다. 



또한 자바 문자열의 경우 원시 문자열 대비 추가로 거의 40바이트가 추가된다. 




또한 Set, List, Queue, ArrayList, Vector, LinkedList, PriorityQueue, HashSet, LinkedHashSet, TreeSet 등과 같은 자바 컬렉션 클래스도 사용된다. 



반면에 연결 데이터 구조(예, LinkedList)는 너무 복잡해서 데이터 구조의 각 항목에 대한 래퍼(wrapper) 오브젝트가 있기 때문에 너무 많은 공간을 차지한다. 



한편 원시 타입의 컬렉션은 java.lang.Double과 java.lang.Integer와 같은 박스형 오브젝트이기 때문에 메모리에 저장한다.



Posted by 김용환 '김용환'



https://www.slideshare.net/dgomezg/parallel-streams-en-java-8


Parallel streams in java 8 from David Gómez García
Posted by 김용환 '김용환'


Date를 GMT스타일(is8601)의 표현 문자열(momentjs가 표현하는 날짜시간 문자열)로 변경하는 예제이다.




Calendar calendar = Calendar.getInstance();

Date date = calendar.getTime();

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");

sdf.setTimeZone(TimeZone.getTimeZone("Asia/Seoul"));


String text = sdf.format(date);

System.out.println(text);

// "2018-01-19T20:34:46.177+09:00"




여기서 XXX의 의미가 중요하다.

XXX는 +09:00를,

X는 +09을 의미한다.




GMT 스타일의 ISO8601문자열을 Date로 변환하려면 다음과 같이 개발한다.


val dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX")
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"))
val date: Date = dateFormat.parse("2017-12-11T13:00:00+09:00")
println(date)

Mon Dec 11 13:00:00 KST 2017





--- 참고로 Joda DateTime의 fomatter는 잘 인식이 안되서 SimpleDateFormat을 사용한다.


val fmt = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ssXXX")




다음 예제는 SimpleDateFormat과 JodaTime을 이용해 UTC로 리턴하는 예이다.


def getUTCDateTime(dateTime: String): DateTime = {
val dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX")
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"))
new DateTime(dateFormat.parse(dateTime), DateTimeZone.UTC)
}



println(getUTCDateTime("2017-12-11T07:00:00+09:00"))의 결과는 2017-12-10T22:00:00.000Z이다.

Posted by 김용환 '김용환'



자바 1.8에서 gc 로그 설정을 잘 해야 한다. 그냥 파일에 출력만 하기로 결정한다면 큰 이슈가 발생할 수 있다.

즉 gc로그를 jvm에서 저장하고 있기 때문에 jvm에 크게 영향을 줄 수 있다.


kafka 서버의 gc로그를 간단히 설정한 결과는 다음과 같다. 5G의 메모리가 저장되어 있다.



$ cp /dev/null kafkaServer-gc.log

cp: overwrite `kafkaServer-gc.log'? y


$ ls -al kafkaServer-gc.log

-rw-r—r— 1 kafka kafka 0  1월 12 10:42 kafkaServer-gc.log


$ ls -al kafkaServer-gc.log

-rw-r—r— 1 kafka kafka 5727854571  1월 12 10:43 kafkaServer-gc.log



재시작을 해보면 gc 로그 크기만큼 jvm메모리가 변경되어 있다. 


따라서 jvm에서 gc로그를 특정 크기로 여러 개의 파일로 설정해야 한다.


Posted by 김용환 '김용환'



jvm의 gc 로그를 사용할 때 아래와 같이 사용하는 것이 좋다. 디스크 용량을 넘어서지 않도록 용량/크기를 제어해야 한다.


gc 로그 파일을 작은 크기로, 여러 개로 나누거나


JVM_FLAGS="-verbosegc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:logs/gc.log -XX:+UseGCLogFileRotation -XX:GCLogFileSize=1m -XX:NumberOfGCLogFiles=100 "



gc 로그파일을 크고 작은 크기로 나눠도 좋을 것 같다.



JVM_FLAGS="-verbosegc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:logs/gc.log -XX:+UseGCLogFileRotation -XX:GCLogFileSize=100m -XX:NumberOfGCLogFiles=5"

Posted by 김용환 '김용환'


self time - 메소드에서 소요된 전체 시간(락/블러킹 포함)


self time(cpu) - 메소드에서 소요된 전체 시간(락/블러킹 제외) 


두 값을 비교하면 멀티 쓰레드 환경 관점으로 특별히 비교할 수 있다.

Posted by 김용환 '김용환'


플랫폼 코드를 개발할 때 특정 클래스의 필드를 저장하고 다뤄야할 때가 있다. 


이 때 java reflection을 이용하면 매우 유용하다. .



개발 중에 에러가 다음과 같이 발생했다. 




Exception in thread "main" java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl cannot be cast to java.lang.Class




에러가 발생한 부분은 다음과 같다.


public List<Map<String, String>> mapOfList;




이 문제는 generic of generic의 문제이다. 



Before


for (Class<?> modelClass : getClasses()) {

for (Field field : modelClass.getDeclaredFields()) {

Class<?> fieldType = field.getType();


if (Collection.class.isAssignableFrom(fieldType)) {

ParameterizedType fieldGenericType = (ParameterizedType) field.getGenericType();


Class<?> classOfCollection = null;

if (fieldGenericType.getActualTypeArguments().length > 0) {

Type genericType = fieldGenericType.getActualTypeArguments()[0];

if (genericType != null) {

classOfCollection = (Class<?>) genericType; // 여기서 발생했다.

}

fieldType = classOfCollection;

}

}

    //...

  }

  //..

}




다음처럼 바꾸면 제대로 동작한다. 




After



for (Class<?> modelClass : getClasses()) {

for (Field field : modelClass.getDeclaredFields()) {

Class<?> fieldType = field.getType();


if (Collection.class.isAssignableFrom(fieldType)) {

ParameterizedType fieldGenericType = (ParameterizedType) field.getGenericType();


Class<?> classOfCollection = null;

if (fieldGenericType.getActualTypeArguments().length > 0) {

Type genericType = fieldGenericType.getActualTypeArguments()[0];

if (genericType != null) {

if (genericType instanceof Class) {

classOfCollection = (Class<?>) genericType;

} else if (genericType instanceof ParameterizedType) { // supports generic of generic 

Type rawType = ((ParameterizedType) genericType).getRawType();

classOfCollection = (Class<?>) rawType;

}

}

fieldType = classOfCollection;

}

}

    //...

  }

  //..

}





Posted by 김용환 '김용환'




import java.util.Arrays;

import java.util.List;

import java.util.Map;

import java.util.stream.Collectors;


import org.apache.commons.lang3.StringUtils;


import org.junit.Test;

import org.springframework.util.Assert;


public class Java8Test {

class Member {

int id;

String name;

public Member(int id, String name) {

this.id = id;

this.name = name;

}

}

@Test

public void test1() {

Member member1 = new Member(1, "samuel");

Member member2 = new Member(2, "keans");

Member member3 = new Member(3, "xy");

List<Member> members =  Arrays.asList(member1, member2, member3);

Map<Boolean, List<Member>> results = members.stream().collect(Collectors.partitioningBy(m -> StringUtils.startsWith(m.name, "x")));

results.forEach((k, v) -> System.out.println("key:" + k + ", value:" + v.stream().map(member -> member.name).collect(Collectors.joining(", "))));

Assert.isTrue(results.get(true).get(0) == member3);

Assert.isTrue(results.get(false).get(0) == member1);

Assert.isTrue(results.get(false).get(1) == member2);

}

}




결과는 다음과 같다.


key:false, value:samuel, keans

key:true, value:xy




단순히  Collectors.partitionBy 대신 Collectors.groupingBy로 사용할 수도 있다. 


import java.util.Arrays;

import java.util.List;

import java.util.Map;

import java.util.stream.Collectors;


import org.apache.commons.lang3.StringUtils;


import org.junit.Test;

import org.springframework.util.Assert;


public class Java8Test {

class Member {

int id;

String name;

public Member(int id, String name) {

this.id = id;

this.name = name;

}

}

@Test

public void test1() {

Member member1 = new Member(1, "samuel");

Member member2 = new Member(2, "keans");

Member member3 = new Member(3, "xy");

List<Member> members =  Arrays.asList(member1, member2, member3);

Map<Boolean, List<Member>> results = members.stream().collect(Collectors.groupingBy(m -> StringUtils.startsWith(m.name, "x")));

results.forEach((k, v) -> System.out.println("key:" + k + ", value:" + v.stream().map(member -> member.name).collect(Collectors.joining(", "))));

Assert.isTrue(results.get(true).get(0) == member3);

Assert.isTrue(results.get(false).get(0) == member1);

Assert.isTrue(results.get(false).get(1) == member2);

}

}




groupingBy메소드에서는 Aggregation 기능도 제공한다.




import java.util.Arrays;

import java.util.List;

import java.util.Map;

import java.util.function.Function;

import java.util.stream.Collectors;


import org.junit.Test;


public class Java8Test {

class Member {

int id;

String name;

public Member(int id, String name) {

this.id = id;

this.name = name;

}

public String toString() {

return id + ", " + name;

}

}

@Test

public void test1() {

Member member1 = new Member(1, "samuel");

Member member2 = new Member(2, "keans");

Member member3 = new Member(3, "xy");

Member member4 = new Member(4, "keans"); // same name, but not same person

List<Member> members =  Arrays.asList(member1, member2, member3, member4);

Map<Member, Long> results = members.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

results.forEach((k, v) -> System.out.println("key:" + k + ", value:" + v));

System.out.println("");

Map<String, Long> otherResults = members.stream().collect(Collectors.groupingBy(m-> m.name, Collectors.counting()));

otherResults.forEach((k, v) -> System.out.println("key:" + k + ", value:" + v));


}

}


결과는 다음과 같다.


key:2, keans, value:1

key:4, keans, value:1

key:1, samuel, value:1

key:3, xy, value:1


key:xy, value:1

key:samuel, value:1

key:keans, value:2






difference 예제이다. map의 요소 중 특정 key를 제외하고 싶을 때 사용할 수 있는 예제이다.

먼저 groupingBy 또는 parittioningBy를 사용하지 않는 예제이다. 



Map<Integer, Object> maps = Maps.newHashMap();

maps.put(1, "samuel");

maps.put(2, "jackson");

maps.put(3, "ice");

maps.put(4, "xxx");

List<Integer> excludeList = Arrays.asList(4);

List<Integer> keys = maps.entrySet().stream().map(k -> k.getKey()).collect(Collectors.toList());


Map<Integer, Object> map = (Map<Integer, Object>) CollectionUtils.subtract(keys, excludeList).stream().collect(Collectors.toMap(p->p, p->maps.get(p)));

System.out.println(map);



결과는 다음과 같다.


{1=samuel, 2=jackson, 3=ice}




CollectionUtils.subtract 대신 Guava의 Collection2.filter를 사용할 수 있다.
Map<Integer, Object> maps = Maps.newHashMap();
maps.put(1, "samuel");
maps.put(2, "jackson");
maps.put(3, "ice");
maps.put(4, "xxx");
List<Integer> excludeList = Arrays.asList(4);
List<Integer> keys = maps.entrySet().stream().map(k -> k.getKey()).collect(Collectors.toList());
Map<Integer, Object> map = Collections2.filter(keys, Predicates.not(Predicates.in(excludeList)))
.stream().collect(Collectors.toMap(p->p, p->maps.get(p)));
System.out.println(map);




partitioningBy를 사용하면 다르게 구현할 수도 있다. 

Map<Integer, Object> maps = Maps.newHashMap();

maps.put(1, "samuel");

maps.put(2, "jackson");

maps.put(3, "ice");

maps.put(4, "xxx");

List<Integer> excludeList = Arrays.asList(4);

Map<Boolean, Map<Object, Object>> map = maps.entrySet().stream().collect(

Collectors.partitioningBy(p -> !excludeList.contains(p.getKey()), Collectors.toMap(p -> p.getKey(), p -> p.getValue()))

);

System.out.println(map);

System.out.println(map.get(true));


결과는 다음과 같다. 



{false={4=xxx}, true={1=samuel, 2=jackson, 3=ice}}

{1=samuel, 2=jackson, 3=ice}


Posted by 김용환 '김용환'