JVM 신규 기능 지원(invokedynamic을 통한 String concatenation)(1.5에 기본값으로 변경 예정)
* JDK9 부터 String 을 합치는 부분은 dynamic method invocation(invokedynamic)을 통해 수행한다.
* 이전보다 더 빠르고 메모리를 적게 소모하며 바이트코드 변경없이 최적화 할수 있는 여지를 제공한다.
* ex) 이전에는 StringBuilder로 하나한 연결 했던 바이트 코드를 생성했기 때문에 최적화할 여지가 더 없지만, ivokedynamic을 통해 bootstrap method를 호출하는 형태의 바이트코드로 생성되고 bootstrap method는 호출되는 시점에 생성되기 때문에 최적할 여지가 생김(Bootstap에 호출은 보통 StringConcatFactory.makeConcatWithConstrants를 호출한다고 함
("var ${var}" 도 그렇고.. data class도 그렇고 null check도 그렇고 너무 많네요..;;)
몰랐던 기능이 있어 기록을 남깁니다.
먼저 "default argument"
자바에서 보통 인자가 없을때 그리고 인자가 있을때 처리를 위해
메소드 오버로딩으로 처리하곤 합니다
이코드가 결국에는 default 값 처리를 위한 건데
결국 arg가 없는 메소드를 열어봐야 기본값을 알 수 있습니다.
public void hello(){
hello("kim");
}
public void hello(String name){
System.out.println("Hello " + name);
}
/////////아니면 아래처럼 null로 넘기고 null이면 기본값 처리하는 방법이..
public void hello(){
hello(null);
}
public void hello(String name){
String localName = name;
if(localName==null){
localName="kim";
}
System.out.println("Hello " + localName);
}
그런데 코틀린에서는 아래와 같이 코드를 작성해서 default argument를 설정할 수 있습니다.
fun hello(name: String="kim")=println("hello ${name}")
훨씬 간결해집니다.
그럼 인자가 많아지거나 다양해지면 어떻게 하나?
(몇개는 입력을 받고 몇개는 default로 처리하고 싶을때... )
자바로 작성하면 null체크를 덕지덕지 처리를 해주기도하고
overload 메소드를 거의 종류별로 만들어야 하는 경우도 생기기 때문에
args가 늘어나는것 대신 보통 input용 클래스를 빌더패턴으로 만들던 그냥 data 클래스로 만들던 만들어서 코드를 처리하기도 합니다.
public void order() {
order(null, -1);
}
public void order(int cnt) {
order(null, cnt);
}
public void order(String menuName) {
order(menuName, -1);
}
public void order(String menuName, int cnt) {
String localName = menuName;
if (localName == null) {
localName = "대표메뉴";//default Menu
}
int localCnt = cnt;
if (localCnt == -1) {
localCnt = 1;// default Cnt
}
System.out.println(menuName + " order " + cnt);
}
코틀린에서는 "named argument"를 써서 쉽게 처리할 수 있습니다.
fun order(menuName: String="대표메뉴", cnt: Int=1)=println("${menuName} order ${cnt}")
여기까지는 위에 default argument와 동일합니다.
여기서 중요한건 사용할때 기존 order(null,10) 형태로 호출하는것이 아니라
아래와 같이 인자를 줄때 변수을 명시해서 넘길수 있습니다.
order("피자", 5)//결과적으로 "피자 order 5", 모두 채운 기존 형태
order(3)//에러
order("피자")//결과적으로 "피자 order 1"
order(menuName = "피자")//결과적으로 "피자 order 1"
order(cnt = 3)//결과적으로 "대표메뉴 order 3"
order(menuName = "피자", cnt = 5)//결과적으로 "피자 order 5"
order(cnt = 5, menuName = "피자")//결과적으로 "피자 order 5", 순서 상관없이 잘 동작합니다
named argument 덕분에 첫번째 인자가 어떤 변수인지 두번째 인자가 어떤 arg인지
메소드 선언을 보지 않아도 호출하는 쪽에서 변수명을 명시하면 혼동을 덜할수 있고
arg순서가 바뀌거나(물론 유지보수를 위해 메소드 arg를 막 변경하는 사람은 없겠지만) arg가 늘어나는 경우에도
연산을 할 때마다 새로운 객체를 만들기 위해 메모리 할당을 한다면 성능상 문제가 생길 수 있기 때문에
(보통 Object + Object는 새로운 Object를 생성... 숫자 연산마다 메모리 할당이 생긴 하다.. 문제가 되겠죠)
primitive type을 두어 primitive는 heap에 할당하지 않고 스택에 value를 바로 value를 저장하여 성능 향상한 것으로 알고 있습니다.
primitive를 통해 성능 향상한 것은 좋지만, java Collection을 통해 int를 담거나 제네릭으로 타입을 처리하거나
할 때 primitive로는 처리가 불가능하기 때문에
primitive를 wrapping 타입(Integer)으로 변경해야 하는 경우가 필요하곤 합니다.
물론, java에서 자동으로 primitive에서 wrapping 타입 변환(auto-boxing) wrapping에서 primitive(auto-un-boxing)을 해주기도 하지만, 편리할 뿐 boxing 때마다 object가 생기고 사라지기 때문에 그리 성능상 좋지는 않고 불편하기도 합니다.
(NPE 문제.. 타입 처리 문제...)
불편하기도 하지만 성능 때문이라도 계속 primitive 타입이 존재하죠..
그래서, 많은 연산 시에는 wrapping 타입 대신 primitive 타입을 사용하라곤 합니다.
int[] values = {...};
int sum=0;
for(val : values){
sum+=val;
}
Integer sum=0;//이렇게 하지 말라곤 하죠
for(val : values){
sum+=val;//이렇게 하지 말라곤 하죠
}
그런데!
코틀린은 이 불편한 primitive가 없다고 합니다.
(실제로 코틀린은 int 대신 Int, double 대신 Double)
없어져서 좋을 수 있지만, "그럼 코틀린은 wrapping 타입만 사용한다는 걸까? 그럼 느리지 않을까?"
라는 의문이 생겼습니다.
그럼 위에서 언급한 연산 때마다 객체가 생기고 사라지고를 반복해서 문제가 생기지 않을까요?
그래서 테스트해봤습니다.
테스트 방법은
1. 테스트 코드 작성
2. class 파일 생성
3. 디컴파일러로 class 파일 확인
을 통해 어떻게 class 파일이 생성되는지 확인하려고 합니다.
1. 자바
먼저, 자바로 primitive일 때
int number_1 = 100;
number_1 += 10;
int number_2 = 200;
number_2 += 10;
int sum = number_1 + number_2;
System.out.println(String.format("sum : %d", sum));
하단은 디컴파일러를 통해 본 자바 primitive 코드
거의 차이가 없습니다. 다만 마지막 라인에 String.format()에
마지막 arg는 Object []이기 때문에 Integer.valueOf()로 오토 박싱 한 부분이 보입니다.
지금까지 객체는 마지막 한 번만 객체 생성이 되겠네요
(물론 정확하게는 Integer.valueOf()가 내부적으로 범위 값만큼은 객체를 캐싱을 하고 있기 때문에 객체를 만들지 않겠지만요..)