java.lang.reflect의 핵심 리플렉션 기능을 이용하면 메모리에 적재된 클래스으 ㅣ정보를 가져오는 프로그램을 작성할 수 있다. Class객체가 주어지면, 해당 객체가 나타내는 클래스의 생성자, 메서드, 필드 등을 나타내는 Constructor,Method,Field 객체들을 가져올 수 있다. 게다가 이렇게 얻은 객체들로 거기 연결되어 있는 실제 생성자, 메서드, 필드들을 조작할 수 있다. 하지만 이런 능력에는 대가가 따른다.

  • 컴파일 시점에 자료형을 검사함으로써 얻을 수 있는 이점들을 포기해야 한다. 리플렉션을 통해 존재하지 않는,또는 접근 할 수 없는 메서드를 호출하면 실행 도중에 오류가 발생할 것이다. 그러니 특별히 주의해야 한다.
  • 리플렉션 기능을 이용하는 코드는 가독성이 떨어진다. 
  • 성능이 낮다. 리플렉션을 통한 메서드 호출 성능은 일반적인  메서드 호출에 비해 훨씬 낮다.
 리플렉션 기능은 원래 컴포넌트 기반 응용프로그램 저작도구를 위해 설계된 기능이었다. 그런 도구는 보통 요청에 따라 클래스를 메모리에 올린 다음, 리플렉션 기능을 통해 어떤 메서드와 생성자가 지원되는지를 알아낸다. 하지만 일단 완성된 프로그램은 리플렉션을 더 이상 사용하지 않는다. 리플렉션을 설계할 때만 활용된다. 따라서 일반적인 프로그램은 프로그램 실행 중에 리플렉션을 통해 객체를 이용하려 하면 안된다는 것이다. 

 하지만, 이러한 리플렉션을 아주 제한적으로만 사용하면 오버헤드는 피하면서도 리플렉션의 다양한 장점을 누릴 수 있다. 객체 생성은 리플렉션으로 하고 객체 참조는 인터페이스나 상위 클래스를 통하면 된다. 

요약

리플렉션은 특정한 종류의 복잡한 시스템 프로그래밍에 필요한, 강력한 도구다. 하지만 단점이 많다. 컴파일 시점에는 알 수 없는 클래스를 이용하는 프로그램을 작성하고 있다면, 리플렉션을 사용하되 가능하면 객체를 만들 때만 이용하고, 객체를 참조할 때는 컴파일 시에 알고 있는 인터페이스나 상위 클래스를 이용하라.



인자의 자료형으로는 클래스 대신 인터페이스를 사용해야 한다는 것이다. 만일 적당한 인터페이스 자료형이 있다면 인자나 반환값, 변수, 그리고 필드의 자료형은 클래스 대신 인터페이스로 선언하자. 객체의 실제 클래스를 참조할 필요가 있는 유일한 순간은, 생상자로 객체를 생성할 때다. 


인터페이스를 자료형으로 쓰는 습관을 들이면 프로그램은 더욱 유연해진다. 가령 어떤 객체의 실제 구현을 다른 것으로 바꾸고 싶다고 하자. 호출하는 생성자 이름만 다른 클래스 이름으로 바꾸거나, 아니면 호출하는 정적 팩터리 메서드만 다른 것으로 바꿔주면 된다.


위와 같이 바꾸면 기존의 구현했던 다른 부분들은 실제로 구현했던 클래스가(Vector) 뭐였는지 몰랐으므로 ArrayList로 바꿔줘도 상관없다.


그러나 적당한 인터페이스가 없는 경우에는 객체를 클래스로 참조하는 것이 당연하다. 연관된 인터페이스가 없는 객체 생성 가능 클래스의 경우, 그 클래스가 값 클래스인지의 여부와는 상관없이, 그 객체는 클래스를 통해 참조해야 한다. 그런 클래스 기반 프레임워크에 속한 객체는, 구현 클래스 대신에 보통 abstract로 선언되는 기반 클래스로 참조하는 것이 바람직하다.



문자열 연결 연산자 + 는 여러 문자열을 하나로 합하는 편리한 수단이다. 하지만 연결할 것들이 많으면 성능에 문제가 생긴다. n개의 문자열에 연결 연산자를 반복 적용해서 연결하는데 드는 시간은, n^2에 비례한다. 그 이유는 문자열이 변경 불가능하기 때문이다. 


 이 메서드의 성능은 item갯수가 많을수록 형편없다. 성능을 향상시키기 위해서는 String대신 StringBulider를 써서 저장해야 한다.

두 메소드의 성능차이는 엄청난다. 따라서 성능이 걱정된다면 많은 문자열을 연결할 때 + 연산자 사용을 피하고, StringBuilder의 append메서드를 사용하는 것이 좋다.



문자열로 해서는 안되는 일들을 짚어보자

  • 문자열은 값 자료형을 대신하기에는 부족하다. 데이터가 원래 텍스트 형태일 때나 문자열을 사용하고, int나 float 같은 수 자료형이면 수 자료형으로 변환해야 한다. 일반적으로 말하자면, 적절한 값 자료형이 ㅣㅇㅆ다면 그것이 기본 자료형이건 아니면 객체 자료형이건 상관없이 해당 자료형을 사용해야 한다.
  • 문자열은 enum 자료형을 대신하기에는 부족하다.
  • 문자열은 혼합 자료형을 대신하기엔 부족하다. 여러 컴포넌트가 있는 개체를 문자열로 표현하는 것은 좋지 못하다. 

    

위와 같은 코드처럼 문자열을 사용하면, 각 필드를 구하기 위해서는 파싱을 해야하는데, 비용이 많이들고 오류 가능성이 많은 작업이다. 혼합 자료형을 표현할 클래스를 만드는 편이 더 낫다.

  • 문자열은 권한을 표현하기엔 부족하다. 때로, 문자열을 사용해서 기능 접근 권한을 표현하는 경우가 있다.
    

위와 같이 사용하면, 키의 유일성을 보장하기 힘들다. 문자열 대신 위조 불가능 키로 바꾸면 해결된다.

    

요약

더 좋은 자료형이 있거나 만들 수 있을 때는 객체를 문자열로 표현하는 것을 피하라. 제대로 쓰지 못할 경우 문자열은 다른 자료형에 비해 다루기 성가시고, 유연성도 떨어지며, 느리고 , 오류 발생 가능성도 높다. 문자열이 적합하지 못한 자료형으로는 기본 자료형, enum, 혼합 자료형 등이 있다.



자바의 자료형 시스템은 두 부분으로 나뉜다. 하나는 int,double,boolean 등의 기본 자료형이고, 다른 하나는 String과 List 등의 참조 자료형이다. 모든 자료형에는 대응되는 참조 자료형이 있는데 , 이를 객체화된 기본 자료형이라 부른다. 


 자동 객체화와 자동 비객체화가 추가되면서, 기본 자료형과 그 객체 표현형 간의 차이를 희미하게 만들었다. 그런데 그 둘 사이에는 실질적인 차이가 있으므로, 사용할 때 주의해야 한다.

 

기본 자료형과 객체화된 기본 자료형의 차이

  • 기본 자료형은 값만 가지지만 객체화된 기본 자료형은 값 외에도 신원을 가진다. 따라서 객체화된 기본 자료형 객체가 두 개 있을 때, 그 값은 같더라도 신원은 다를 수 있다.
  • 기본 자료형에 저장되는 값은 전부 기능적으로 완전한 값이지만, 객체화된 기본 자료형에는 저장되는 값에는 그 외에 null이 저장될 수도 있다. 객체이기 때문이다.
  • 기본 자료형은 시간이나 공간 요구량 측면에서 일반적으로 객체 표현형보다 효율적이다.

위의 코드에는 심각한 문제가 있다. naturalOrder.compare(new Integer(42),new Integer(42))의 값을 테스트해보면 문제점이 발견된다. compare메소드에서 first < second 를 할때 Integer 자료형은 int로 자동 형변환이 된다. 두 비교되는 숫자가 같거나, second가 더 작을경우 , (first == second ) 를 실행하게 되고 , 여기서 비교되는 두 객체는 객체화된 기본 자료형이다. 객체화 된 기본자료형을 ==으로 비교하니 false가 반환되고 결과값이 1이 발생되게 되는 것이다. 즉 객체화된 기본 자료형에 == 연산자를 사용하는 것은 거의 항상 오류라고 봐야 한다는 것이다.


위의 프로그램은 "Unbelievable"을 출력하지는 않는데, (i == 42 )를 실행할 때 NullPointerException이 발생한다. 기본 자료형과 객체화된 기본 자료형을 한 연산 안에 엮어 놓으면 객체화된 기본 자료형은 자동으로 기본 자료형으로 변환된다. 기본 자료형으로 변환되는 과정에서 객체멤버는 기본값이 null이므로 NullPoniterException이 발생하는 것이다.


그렇다면 객체화된 기본 자료형은 언제 사용해야 하나?

  • 컬렉션의 요소,키,값으로 사용할 때다. 컬렉션에는 기본 자료형을 넣을 수 없으므로 객체화된 자료형을 써야 한다.
  • 형인자 자료형의 형인자로는 객체화된 기본 자료형을 써야한다.

요약

가능하다면 기본 자료형을 사용하라는 것이다. 기본 자료형이 더 단순하고 빠르다. 자동 객체화는 번거로운 일을 줄여주긴 하지만, 객체화된 기본 자료형을 사용할 때 생길 수 있는 문제들까지 없애주진 않는다.  객체화된 기본 자료형 객체 두 개를 ==로 비교한다는 것은 그 두 객체의 신원을 비교한다는 것이다. 객체화된 기본 자료형과 기본 자료형을 한 표식안에 뒤섞으면 비 객체화가 자동으로 일어나며, 그 과정에서 NullPonterException이 발생할 수 있다. 


float과 double 자료형은 이진 부동 소수점 연상을 수행하는데, 이것은 넓은 범위의 값에 대해 정확도가 높은 근사치를 제공할 수 있도록 설계된 연산이다. 하지만 정확한 결과를 제공하지는 않기 때문에 정확한 결과가 필요한 곳에는 사용하면 안된다. 돈과 관련된 계산에는 특히 사용하면 안된다.


 위의 프로그램은 1달러로 최대 몇개의 사탕을 사는지, 거스름돈은 얼마인지 계산하는 프로그램이다.  각 사탕은 최소 10센트부터 최대 1달러까지 10센트씩 비싼 사탕이 있을 때 , 결과값은 어떻게 출력될까? 개수는 3개이며 , 잔돈은 $0.3999999... 로 출력이 된다. float과 double은 정확한 계산이 되지 않기 때문이다. 

 그럼 이러한 문제는 어떻게 해결해야 할까? int , long 또는 BigDecimal로 구현하면 된다. BigDecimal로 구현할 경우 아래와 같은데 속도도 느리며, 사용이 불편하다


BigDecimal의 대안은 int나 long을 사용하는 것이다. 둘 중 어떤 자료형을 쓸 것이냐는 수의 크기, 그리고 소수점 이하 몇 자리까지를 표현할 것이냐에 따라 결정된다.



요약

정확한 답을 요구하는 문제를 풀 때는 float이나 double을 쓰지 말라는 것이다. 소수점 이하 처리를 시스템에서 알아서 해줬으면 하고, 기본자료형보다 사용하기가 좀 불편해도 괜찮으며 성능이 조금 떨어져도 상관없을 때는 BigDecimal을 사용하라. 그러나 성능이 중요하고 소수점 아래 수를 직접 관리해도 상관없으며 계산할 수가 심하게 크지 않을 때는 int나 long을 사용하라.



표준 라이브러리를 사용하면 무엇이 좋을까?

  • 라이브러리를 개발한 전문가의 지식뿐만 아니라 보다 먼저 사용한 사람들의 경험을 활용할 수 있다.
  • 실제로 하려는 일과 큰 관련성도 없는 문제에 대한 해결 방법을 임의로 구현하느라 시간을 낭비하지 않아도 된다.
  • 특별한 노력을 하지 않아도 성능이 점차 개선된다.
이러한 라이브러리를 활용하기 위해서는 새로운 릴리즈가 나올 때마다 많은 기능이 새로 추가되는데, 그때마다 어떤 것들이 추가되었는지를 알아 두는 것이 좋다. 자바 프로그래머 라면 java,lang,java.util안에 내용은 숙지하는 것이 좋으며 , java.io의 내용도 알아 두면 좋다.

요약

흔하게 쓰일법한 무언가를 개발해야 한다면, 아마 라이브러리를 뒤져보면 그에 맞는 클래스를 찾을 수 있을 것이다. 있다면 그것을 사용하라. 일반적으로 보자면 직접 만든 코드보다는 라이브러리에 있는 코드가 더 낫고, 점차 개선될 가능성도 높다. 


for-each 문은 성가신 코드를 제거하고 반복자나 첨자 변수를 오나전히 제거해서 오류 가능성을 없앤다. 


 보기와 같이 for문 사용에 비해 for-each문을 사용하는 것이 훨씬 간결해 보이고, 첨자변수에 대해 문제를 일으킬 걱정이 없다. for-each문의 장점은 여러 컬렉션이 중첩되는 순환문을 만들어야 할 때 더 빛난다. 


for문으로 작성한 위의 코드를 for-each문을 사용하면 아래와 같이 간결해진다.


요약

for-each문은 전통적인 for문에 비해 명료하고 버그 발생 가능성도 적으며, 성능도 for문에 뒤지지 않는다. 그러니 가능하다면 항상 사용해야 한다. 그러나 아래와 같은 경우에는 for-each문을 사용할 수 없다.
  • 필터링 - 컬렉션을 순회하다가 특정한 우너소를 삭제할 필요가 있을 때
  • 변환 - 리스트나 배열을 순회하다가 그 원소 가운데 일부 또는 전체를 변경해야 할 때
  • 병렬 순회 - 여러 컬렉션을 병렬적으로 순회해야 하고, 모든 반복자나 첨자 변수가 발맞춰 나아가도록 구현해야 할 때 


+ Recent posts