앞선 포스트에서 테스트 프로그램을 만들어서 돌리는 간단한 프로그램을 만들었습니다.
물론 내부 로직은 다 빠지고 형태만 보여주기 위한 코드였고요..

최종적으로 나온 Tester.java 의 모습은 아래와 같았습니다.



2개의 테스터 클래스가 보입니다. 내부로직으로 구현되어 있지는 않지만
ProductListsComapreTester.java는 상품 리스트를 검색해서 그 정렬 순서가 원하는 순서대로
정렬되어 있는지 확인하는 Tester 클래스입니다.

ProductRankScoreTester.java는 다른 두개의 검색 서버가 있다고 가정 할 때 각각의 서버에 검색을 하고
그 두개의 결과가 일치하는지 확인하는 Tester 클래스입니다. 물론, 내부 로직은 구현을 하지 않았습니다.

일단, 앞에서 이제 인터페이스를 사용한다고 하였는데
Tester.java를 보시면 어떻게 인터페이스를 만들면 좋을지 대충 짐작도 되실겁니다.

바로 만들어서 그 인터페이스를 사용해보겠습니다.


ProductTester.java
그리고, 이를 구현하도록 위 두개의 테스터 클래스를 수정해보겠습니다.


ProductRankScoreTester.java ProductListsCompareTester.java
이미 isPassed라는 이름의 메서드를 공통적으로 가지고 있었기 때문에 쉽게 수정이 가능합니다. 그럼,
Tester.java는 어떻게 바뀔까요?


Tester.java
각 구현체 클래스를 사용 해 참조 변수를 선언하던 부분이 인터페이스를 사용하여 선언하는 것으로 수정되었습니다.
이 상태로 후배에게 "어떤 이점이 있을 것 같아?" 라고 얘기했더니 tester라는 참조 변수를 재사용 할 수 있다. 라고 대답
하더군요...

ProductTester tester = new ProductListsCompareTester();
boolean isPassed = tester.isPassed();
tester = new ProductRankScoreTester();
isPassed = tester2.isPassed();

"재사용하면 뭐가 좋은데?" 라고 하니
"메모리... " 우물쭈물 대답하더군요...

재사용하는 것은 객체여야하지 변수를 재사용하는 것은 이득이 없다라고 얘기 해주고.. 또, 객체도 어지간히 크거나 무거운 객체가 아니며 재사용 하는 것이 아주 눈에 띄는 성능 향상을 가져오지 않는다고 얘기해주었습니다.

개인적으로는, 변수의 재사용을 잘 하지 않는 편입니다.

아무튼.. 변수의 재사용이 중요한 것이 아니라 참조 변수를 선언하는 데 사용한 타입 (ProductTester 인터페이스)이 같다는 것이
중요한 것 입니다.

테스트를 하기 위한 로직은 ProductTester 인터페이스를 구현한 각각의 구현 클래스들이 알아서 가지고 있고,
외부에서는 isPassed라는 메서드를 통해 그 테스트 결과가 통과인지 실패인지를 알 수만 있으면 됩니다.
그것이 이번에 간단하게 만들어보려고 하는 테스트 프레임워크 (라고 이름 붙이기도 민망한..)의 핵심입니다.

그럼, 인터페이스를 사용하였으니 이를 사용해서 Tester 클래스들을 모두 가져와서
일괄적으로 테스트를 실행 할 수 있는 MultiTester를 만들어보겠습니다.


MultiTester.java
인터페이스를 사용했기 때문에 ProductTester라는 Type을 갖는 List를 사용하여 모든 Tester 클래스를 받고
이를 사용해서 테스트를 일괄적으로 수행하고 있습니다.
addTester메서드의 파라메터가 ProductTester인것과 isAllPassed 메서드의 for문에 마찬가지로 ProductTester 인터
페이스가 사용 된 것을 잘 보시고요...

그럼 이것을 어떻게 사용 할까요?


Tester.java
테스터 클래스들이 같은 인터페이스를 구현하고 있기 때문에
MultiTester는 위 처럼 다른 구현 클래스를 하나의 ProductTester로써 가질 수 있게 됩니다.

테스터 클래스는 현재 사용하고 있는 Service나 Controller 혹은 DAO를 사용하여
테스트를 수행하도록 되어 있다고 가정해보면..
위 Tester를 하루에 한번씩 돌리도록 스케쥴을 지정하고 그 결과로 메일로 발송하게 되면
누군가 Service나 DAO의 로직을 잘못 수정했을 때 위 테스터에 의해서 그 결과를 바로 받을 수 있을 것 입니다.

이게 아주 초보적인 테스트 자동화라고 볼 수도 있겠죠..

만약에 서비스 클래스가 늘어나서 그 서비스 클래스를 테스트 하기 위한 테스터를 만들어
위 자동 테스터 프레임워크에 적용하고 싶다면?

간단합니다..
ProductTester 인터페이스를 구현하고, 이를 MultiTester에 add만 해주면 되는 것이죠..



NewServiceTester.java Tester.java
Tester에 저렇게 추가만 하면 되지요...

그런데, 테스터가 추가 될 때마다 Tester 클래스를 수정해줘야 하는 것은 조금 귀찮을 수 있습니다.
다음에는 이것을 프로퍼티를 사용해서 클래스 수정 없이 테스터를 추가해주는 것을 보겠습니다.

reflect에 대한 이야기가 나오겠고요...

그 이후에는 Annotation을 사용해서 아예 프로퍼티 조차 수정하지 않아도
테스터가 적용 되도록 해보겠습니다....


Posted by 용식
Java책을 보면서 공부를 하면
Interface에 대한 내용을 볼 수 있습니다.

제 기억으로 처음 자바를 공부하는 입장에서 가장 기억에 남았던 것은
인터페이스는 내용 없는 메서드 정의가 가능하고, 이것을 구현하는 클래스들은
만드시 그 메서드의 내용을 구현해야 한다. (추상 클래스는 여기서 논외로 하겠습니다.)
라는 것이었습니다.

그리고 나오는 예제들이 TV나 그런거였고..
여기서 다형성과 캐스팅에 대한 얘기들도 같이 나옵니다.

그렇게해서 Interface를 공부했는데..
그렇게 공부를 하고 몇 년이 지나도록 업무에 있으면서
도무지 이 Interface가 왜 좋은지 이해를 못 하고 있었습니다.

Interface로 메서드를 정의하고
그것을 구현하는 클래스들이 항상 같은  메서드를 갖도록 한다.

이것이 사람/동물 이런 관계로 OOP를 설명하는 책의 내용과 맞물려서
관련이 있는 is-a 관계가 성립하는 클래스들끼리 인터페이스를 정의해서 사용하는구나..
싶었는데.. Cloneable이나 Comparable 같은 것을 보면 또 그렇지도 않습니다.

최근에 부사수(2년차)에게 검색에서 리스트 2개를 가져와서
비교하는 테스트 프로그램을 만들라고 하였습니다. Junit 같은 것을 사용해도 되는데
좀 더 기본적인 것들을 가르쳐주고 싶어서 일단 마음대로 구현해보라고 하였습니다.

그러는 와중에 나온 것들을 정리하면 좋겠다 싶어서 여기다가 정리해봅니다.

후배가 처음 가져온 소스는 main 메서드를 사용해서
작성한 프로그램이었습니다.



Tester.java

기능은 잘 작동하는데 좀 더 가르쳐주기 위해서
테스트를 수행하는 메서드를 하나의 클래스로 다시 만들라고 하였습니다.
그리고 결과를 print하지말고 boolean으로 return하여 테스트를 통과했는지 못 했는지를 나타낼 수 있도록
네이밍을 다시 하라고 하였습니다.

그래서 아래와 같은 클래스가 만들어졌습니다.

ProductListsCompareTester.java

실행은 아래처럼 될 것 입니다.
Tester.java
이 상태에서 이번에는 검색을 한 다음 그 정렬순서가
점수에 맞게 되었는지 확인하는 테스트케이스를 더 만들어보라고 하였습니다.
ProductRankScoreTester.java

사용은 아래와 같이 될 것 입니다.
Tester.java

이렇게 해서 두개의 테스터 클래스가 생겼습니다.
ProductListsCompareTester와  ProductRankScoreTester 이렇게 두개의 클래스가 생긴거죠..

물론 같은 이름의 메서드를 사용하도록 제가 유도를 하기는 하였지만..
이렇게 하고 나서 후배에게 "공통되는 메서드를 인터페이스로 빼서 인터페이스를 만들고
저 Tester 클래스들을 인터페이스를 상속하게 하여 구현하면 어떨까? 라고 물어보고 그렇게 작업을 해보라고 하였습니다.

이제 한번 인터페이스를 활용해보려고 합니다.
Posted by 용식
코드 컴플리트를 읽다보니 나온 이야기인데..
여기서 소개되는 여러가지 디버깅 기법 중
"고백을 통한 디버깅"이 나온다.

이클립스 같은 툴에서 제공되는 디버깅 모드도 굉장히 강력하고
즐겨 사용하는데..

이 고백을 통한 디버깅 또한 심심찮게 겪게 되는 것이라
많이 공감이 간다.

고백을 통한 디버깅은 이른바..

"OO대리님~ 잠깐 시간 좀 있으세요? 제가 ~~ 코드를 작성을 했는데, 정렬이 잘 안되거든요. 원래 하려던거는.. 이렇게 이렇게 하면 정렬이 이렇게 이루어지고.. 저렇게 하면 저렇게 이루어져야 하는데.. 데이터 입력이 되면서 바로 정렬이 되도록.. 엇.. 아니다. 정렬이 안되는구나.. 핫.. 감사합니다~"

이런 상황... ㅋ

듣는 사람은 "응 어? 응" 이런 상황이 되는데..
들어주는 사람은 아무말도 안 했는데
물어보는 사람 혼자 북치고 장구치는 상황.

예전 GS에 있을 때
풍대리님이 항상 내가 뭔가 여쭤보면
항상 "니가 지금 하고 싶은게 뭐고 안 되는게 뭔지 정확히 얘기해봐" 라고 하셨었는데..
지금 생각해보면 그렇게 얘기하다가 혼자 풀려서 "대리님 감사합니다."를
얘기했던 적도 많은 것 같다. ㅋㅋ

참 희한하다..

자기가 구현한 로직과 코드를 말로 설명하다보면
자연스럽게 그것들이 정리가 되는 것 같다.

정말.. 몇 시간 동안 씨름하던 문제를
"아.. 모르겠다... OO대리님께 여쭤봐야겠다" 라고 생각하고
설명을 하다가 정말 아무렇지도 않게 문제가 해결 되는 경우가 종종 있는데
참 재미있는 것 같다.

그리고 또 하나가 있다.

잠시 떨어져 생각하기..

아무리 봐도 모를 때는
잠시 다른 일을 한다던가 휴식을 취한다던가 하다보면
불현듯 떠오르는 해결 방법 혹은 접근 방법이 있다.

이런것도 디버깅에 큰 도움이 되는 것 같다.

이 방법의 문제는
"아~~ 내일 이렇게 한번 해봐야겠다!!!" 라고 퇴근 길에 번개 같은 아이디어가
생각났는데도 불구하고 다음날 그 사실을 까먹고
다시 삽질을 하게 되는 경우도 있다는 것...

결론은..
이래저래 사람의 머리는 참 신비하다? -_-;;
Posted by 용식
간단한 소스를 보겠습니다.


어떤 문제가 보이시나요?

일단, 변수 i를 두개의 for문에서 조건 counter로 사용하고 있는 것이 보이실 것 입니다.
이런 경우 i를 여러 for문이 공유하게 되면 문제가 발생 할 가능성이 커지고
디버그 하기도 매우 어렵습니다.

일반적으로, 변수는 사용하기 바로 직전에 초기화 하는 것이 좋다고 하죠..
위와 같은 경우는
for(int i = 0; i< 10; i++) {
..
}
이렇게 되겠네요..

중요한 것은 여기서 얘기하고 싶은 것은 변수의 초기화 시점이 아닙니다.
i라는 변수는 for문에서 loop를 계속 돌릴지 빠져나갈지를 결정하는 제어변수로 사용되고 있습니다.
그런 변수 i를 for문 내부에서 임의로 값을 할당하여 변경하고 있습니다.

int result = getXXXXX();
i = result;

어떤 의도였는지 잘 모르겠지만 (위와 같은 소스의 문제점 중 하나가 바로.. 소스를 읽는 사람이 그 의도를 파악하기 어렵다는
것 입니다.)

아무튼 위와 같은 로직이 되어 있을 때 getXXXXX(); 메서드가 0을 리턴하면 어떤 일이 벌어질까요?

실제로 전체 800라인이 넘어가는 메서드 안에
저렇게 i를 공유한 for문이 6개 정도 있는 클래스가 있었습니다.

어디선가 무한루프가 돌아가는데
메서드의 크기도 길고
하나의 for문 자체도 길다보니 실제 개발을 한 사람도 2-3시간 동안
원인을 찾지 못 하고 있었습니다.

개발 해 놓고 1-2주 지나면.. 또 금새 가물가물한 것이 인지상정 --;

몇 가지 원칙을 가지고 보면 대략 문제가 될만한 곳이 보입니다.

1. for문에서 카운터 i를 다른 for문과 공유하지 말 것.
2. 사용하기 바로 직전에 초기화 할 것.
3. 카운터 i를 for문 로직안에서 변경하지 말 것. (특정 조건에 의해 loop를 빠져나가야 한다거나 한다면, for문 대신 while문을 고려해 볼 것)
4. loop의 길이는 한페이지에 다 보일 수 있을 정도의 길이를 유지하도록 노력 할 것.


위 내용은 effective java나 code complete와 같은 책에 항상 나오는 내용입니다.

실제로 3번을 지키지 않아 무한 로프가 돌고 있는 메서드였으며
이것을 찾아내는데 10분도 걸리지 않았습니다.

요즘 이클립스등 개발 툴이 좋아져서 보고 싶은 변수 찍으면 어디어디에 사용되고 있는지
다 나오니까.. 더 쉽죠..



Posted by 용식
웹 사이트에서 "정렬" 이라던가 파라메터에 따른 "로그"등을 남길 때 if문이 많이 사용 됩니다.
일반적으로 처음 사이트를 구축시
파라메터를 정해 놓습니다.
정렬에 대한 파라메터..

처음에는 각 파라메터와 그 파라메터가 뜻하는 것들을 주석으로 잘 달아 놓습니다.
그리고 이것을 처리하는 클래스는 일반적으로 아래와 같은 모양을 갖게 되는 경우가 많습니다.


이제 사이트가 만들어지고 , 다른 개발자들에 의해서 유지보수가 되고
요구사항으로 정렬조건등이 늘어나게 되면서
처음 달아놨던 주석은 엉망이 되고, QueryBuilder의 if문은 요구사항이 생길 때 마다 늘어나게 됩니다.
그나마 Builder 클래스를 로직이 필요한 곳에서 모두 공통으로 사용하고 있다면 상황은 좋은 편이겠지만
혹시라도 위 로직이 여기저기 퍼져있다면, 정렬 조건이 하나 추가 될 때마다 손대야 하는 클래스도 많아지고
if를 통한 분기문이 길어지면서 수정 시 오류를 일으킬 확률도 커지고...
보기도 안 좋아집니다.

그리고, 파라메터를 set하는 부분에서도 파라메터의 타입이 String이기 때문에
사실 P,N 등 개발자가 처음 설계 했을 때 의도했던 문자가 아닌 "POP", "NEW"등 어떤 String이 들어가도
이 소스는 에러를 일으키지 않습니다. 다만, 정상적으로 작동을 하지 않을 뿐이지요..

때문에 이런 정렬 타입이 추가 될 때마다 어딘가에 정리를 해야하고
유지보수 하는 사람은 이 정리 된 문서가 있어야 각각의 파라메터가 뜻하는 것을 알 수가 있습니다.

주석이라도 최신화가 되어 있다면 조금 낫겠지만
사실 그렇게 되기는 많이 어렵죠.


이미 제가 있는 이곳에도 저런 코드가 있어서..
이번에 enum을 활용해서 위 코드를 정리해보았습니다.

일단 enum 클래스를 정의 합니다.


이런식이 되겠죠.. 일단 골격만 잡았습니다.
그리고 RequestDomain 클래스에서 이 enum을 사용하도록 해야 합니다.


일단 이렇게 해봤는데..
레거시에서는 이미 파라메터를 String으로 Setting해주고 있습니다.
이 부분들을 모두 찾아 다니면서 SortCodeEnum 타입으로 set해주도록 수정하면 좋겠지만
현실적으로 어려운 일입니다. 그래서, 기존의 레거시를 최대한 활용하는 쪽으로 방향을 잡았습니다.


enum 클래스를 수정하여 생성자에서 코드의 뜻을 받아주도록 하였습니다.
그리고 그 코드의 로그를 받아 낼 수 있는 getter 메서드 (getCodeName)를 만들었습니다.
또한, 각 enum의 타입들이 쿼리를 가지고 그것을 리턴 할 수 있도록 getSortQuery 메서드를 정의 하여
각 enum 타입에서 오버라이드 하고 있습니다.

그럼 이것을 RequestDomain 클래스에서는 어떻게 사용할까요.


기존의 코드들이 이미 String으로 된 파라메터를 사용하고 있기 때문에
valueOf 메서드를 활용하여 그 파라메터를 가지고 SortCodeEnum 을 생성하여 그에 따른
CodeName과 SortQuery를 받아오고 있습니다.

이것을 QueryBuilder 클래스에 적용하면 아래와 같이 됩니다.

이렇게 enum으로 수정 할 때 제가 생각하는 장점은 이렇습니다.

1. 소스 그 자체가 최신화 된 정보를 가지고 있습니다.
 - 저는 프로그램을 개발하면서 가장 중요하다고 생각하는 것이 코드가 읽기 쉽게 작성되어야 한다. 라는 것 입니다.
   이 방식은 enum 클래스 그 자체가 항상 최신화가 되면서, 그 자체로 자기가 가지고 있는 뜻을 모두 보여주고 있습니다.
   특별히 주석을 보지 않아도, 문서를 만들지 않아도 유지보수를 하는 사람의 입장에서는 큰 문제는 없을 것 입니다.
2. 과도한 분기문을 줄여, 소스를 간단하게 만들 수 있습니다.
3. 정렬조건 추가나 수정등의 요구사항에 간단하게 대처 할 수 있습니다.
4. 위 예제에서는 sortCode 를 String으로 set 해주고 있지만, 만약 code 자체를 set 할 때도 SortCodeEnum 타입으로
set 할 수 있게 해준다면, 사용하는 입장에서는 그만큼 오류를 일으킬 확률이 줄어듭니다. 정해진 타입만 파라메터로
넘겨줄 수 있기 때문이죠.

위 코드에서 주의 할 부분이 있습니다.
enum의 valueOf 메서드를 사용하고 있는데..
만약 valueOf메서드의 파라메터인 sortCode가 SortCodeEnum 클래스에 정의되어 있지 않는다면
위 소스는 exception을 발생시킵니다. 따라서, 이에 따른 방어코드가 필요합니다.

위에서는 RequestDomain에 SortCodeEnum이 들어갔지만..
그냥 QueryBuilder 쪽에서 SortCodeEnum을 생성하여 처리하는 방법도 있을 것 입니다.
exception에 대한 대처는 후자가 더 유용 할 것 같네요.

그리고 enum 타입으로 생성되는 객체들은 싱글턴입니다.
Posted by 용식
TAG enum, java
Collection 같은 경우 generic type을 넣고 빼는 정도의 저장소로써 사용을 해서
그런 일이 없는데..
가끔 Generic Type의 instance를 동적으로
가져오고 싶을 때가 있다...

얼마전에 enum 을 사용한 mapper에 대해서 살짝 쓴적이 있는데..
(http://devyongsik.tistory.com/288)
이 짓을 하기 위해서는 List<String[]> 형의 데이터를  
getter 메서드를 가지고 있는 T type을 사용한 List<T> 형으로
바꿔주어야 했다.

솔루션에서 List<String[]> type으로 결과를 받아서
for문 돌려주면서 만들면 되기는 하는데..
필요한 곳에서 사용 할 수 있는 converter를 만들고 싶었다.


로 해결 되면 좋겠지만..

문제는 이 generic type의 T가
생성자 파라메터로 String[] 형을 가지며
instance로 생성되어야 한다는 것...

예를 들면 이런식인데..


그러면..바꾸고자 하는 type T는 어떤 모양인가..


경우에 따라서 두개의 domain class가 각각 사용된다.

generic type이 컴파일 된 코드에서는 모두 raw 타입으로 변경된다는 것을 생각해보면
T t = new T();
이런게 당연히 될리가 없고 ㅋㅋ

해서 찾아낸 방법이 아래와 같다..
어쩔 수 없이 ListDomain.class와 ImageDomain.class 를 Converter가 넘겨받아
reflect를 사용하여 instance를 생성하도록 하는 것.

일단 생성자에 들어가는 파라메터를 알고 있기 때문에
Converter에서는 큰 문제가 없겠다 싶었다.


일단 동작은 잘 하는데..

사실 generic을 사용 했지만
실제로 들어 갈 수 있는 타입은 딱 2가지 뿐이고..
그것도 생성자의 파라메터가 String[] 형을 가지고 있어야 한다.

이런거면 interface를 만들어서 그것을 상속한 generic type을 사용하는 것이
더 가독성도 좋고, 직관적일 것 같긴 한데..
레거시가 너무 깊게 박혀 있어서.. 그렇게까지는 건드리기가 쉽지 않네...

아무튼.. generic type을 instance화 하는 방법에 대해서는..
관련된 내용이 여기저기 많았다.

http://stackoverflow.com/questions/75175/create-instance-of-generic-type-in-java
http://www.velocityreviews.com/forums/t593591-how-to-create-an-instance-of-type-t.html

Posted by 용식

정규표현식에서...

Java 2010.03.16 14:41

돌리면 아래와 같은 exception..

Exception in thread "main" java.util.regex.PatternSyntaxException: Unexpected internal error near index 1
\
 ^

이유.

What happens is that the compiler sees \\ as an escape for \, so it looks for something after the \\ and finds \h which isn't appropriate. You want it to see \\ so you need to escape the first \ to \\ and you need to escape the second \ to \\.


출처 : http://www.coderanch.com/t/459342/Java-General/java/replacing-string-with-character#2049450

이게 언뜻 봤더니..
sun 홈페이지에 java bug로 옛날에 누가 등록을 했었던 것을
본 것 같다.. --ㅋ

escape 문자에 대해서 원래 알고 있었지만..
"\\\\" 이정도까지 나가면.. 헷갈리기 시작한다.

호접몽처럼.. 내가 나비가 되는 꿈을 꾼 것인지.. 나비가 내가 되는 꿈을 꾼 것인지..~~

이제 escape 문자인지.. 아니면.. escape 문자의 도움을 받아서 표현 된 문자를 나타내는 것인지~~
Posted by 용식
Class.class에 대한 나의 생각.

Class 클래스가 있습니다.
저 처음에 자바 공부 할 때 이 클래스는 도대체 뭔가???? 했었습니다.
String.class 라는 것이 코드 속에 나오는 것도 엄청 신기했습니다.
String.class는 파일로만 존재하는 것인 줄 알았었습니다.

Class 클래스는 무엇일까요.

Java의 OOP를 얘기 할 때 나오는 얘기 중 가장 많이 나오는 것이..
클래스를 만들어 틀을 만들어 놓고 , 이 틀을 사용해 인스턴스를 찍어낸다(?)라는 것입니다.


위 처럼 User 클래스를 만들어서 틀을 만들고

이렇게 인스턴스를 만들어냅니다. 이때 생성 된 User 클래스의 인스턴스들은
모두 이름,주민번호라는 필드와 getName, setName등의 메서드들을 가지고 있게 됩니다.
데이터와 행동을 가지고 있는 것이지요.
이처럼 User에서 만들어지는 객체들이 가져야 할 것들을 정의 하고 있는 것이 User 라는 클래스입니다.

그러면 한단계만 더 위로 올라가보면..
User라는 클래스는 필드와 메서드를 가지고 있습니다.
물론 클래스에는 더 많은 속성들이 존재하지만 일단 논외로 두고요..

일단 필드와 메서드만을 놓고 봤을 때
이렇게 만든 User라는 클래스 즉, User.class도 필드와 메서드를 가지고 있고
java의 기본 클래스인 String.class도 필드와 메서드를 가지고 있는 것이지요.

즉, Java에서 사용되는 Class들에 대한 정의를 하고 있는 틀이 Class.class 입니다.

Java Api 문서에는
Instances of the class Class represent classes and interfaces in a running Java application. Every array also belongs to a class that is reflected as a Class object that is shared by all arrays with the same element type and number of dimensions.
Class 클래스의 객체는 자바 프로그램에서 사용되는 클래스들과 인터페이스들을 나타낸다고 되어 있습니다.

Class.class가 가지고 있는 메서드를 조금 보면 이해가 더 되시리라 생각됩니다.

일단, static으로 되어 있는 forName 메서드가 있습니다.
jdbc 예제에서 많이 나오는 메서드로... Class.forName("java.lang.String"); 으로 실행하면
해당 이름을 갖는 클래스(String)의 정보를 담은 Class instance를 리턴해줍니다.



java.lang.String 클래스의 정보를 가지고 있는 클래스 인스턴스를 얻어서
이름과 메서드에 대한 정보를 출력하는 예제입니다.
이러한 것이 가능한 이유는 자바에서 사용되는 모든 클래스들은 공통된 속성을 가지고 있고 (필드, 메서드등.)
그 속성을 정의하고 있는 것이 Class 클래스이기 때문입니다.

Class 클래스의 객체는 많은 메서드들을 가지고 있습니다. 모두 클래스에 대한 메서드들입니다.

어노테이션,생성자,필드등도 가져 올 수 있고
이것을 사용하여 리플렉트로 객체를 생성 할 수도 있고 상위 클래스들을 가져 올 수도 있습니다.
메서드를 실행 할 수도 있습니다.

new String(); 을 하지 않아도 위 처럼 그 String 클래스가 가진 메서들을 가지고 올 수도 있고
객체도 얻을 수 있고, 메서드를 실행 할 수도 있습니다.

우리가 만든 User 클래스의 객체들은 모두 이름과 주민번호를 갖는다. 라는 것과 비교해보면
좋을 것 같습니다.

결국 Class 클래스를 이용해서 우리가 다루는 모든 클래스에 대한 정보들을 얻어 올 수 있습니다.
Posted by 용식

[Java] Object.equals()

Java 2010.02.10 01:00
equals의 스펙이나 이런 것 보다는 (여기에 잘 나와있습니다.)
클래스를 만드는데 있어서 이 equals라는 메서드가 왜 필요한지..
OOP라는 관점으로 한번 써보려고 합니다. (그래봐야 아주 기초적인 내용입니다.)

사실 이런 것은... 논란의 여지가 있기도 할 것 같아서... 좀 그렇지만..

List는 자바에서 가장 많이 사용 되는 Collections 프레임워크에 속해있는 클래스입니다.
잘 알고 계시다시피..
List는 입력 된 순서대로 저장이 되며, index를 통해 접근한다는 특징이 있습니다.
원소를 가지고 있는지를 파악하는 contains라는 메서드도 가지고 있습니다.

일단 아래와 같은 클래스를 만들어 봅니다.



이름과 나이를 갖는 사람을 클래스화 하였습니다.
아래의 코드를 보겠습니다.


실행 결과는 "false" 입니다.

아마.. "저건 당연한 것 아닌가? user1과 user2가 서로 다른 객체 이니까 저건 당연한 결과지.." 라고 생각 하실 수 있을 것입니다.
그렇다는 얘기는 list의 contains 메서드가 엘리먼트들의 equals 메서드를 사용하여 비교한다는 사실도 아시는 것일 거에요.

그런데.. 업무적으로 본다면..이름과 주민번호가 같은 두개의 사람 객체가 다른 객체라고 볼 수 있을까요?

  JVM 입장에서는 저 두개의 객체는 다른 객체입니다.

하지만 클래스를 설계한 입장에서는 주민번호와 이름.. 아니 주민번호만 같아도
두개의 객체는 같은 사람을 뜻하는 객체라고 생각되어져야 할 것 입니다.
그렇지 않다면 엉망이 되어버릴테니까요...

위와 같이 사람 객체 처럼 단 하나만 생성되어야 하는 클래스들은
싱글턴으로 구현을 하기도 하지만, 일단 거기까지는 생각하지 않고 보면
위 상황은 조금 애매하게 보입니다. 싱글턴으로 구현 된 클래스들은 그 변화가
JVM 전역적으로(엄격히 얘기하면 ClassLoader에..여기) 영향을 주기 때문에 다루기도 쉽지 않습니다.

예전에는 책에서 이야기 하는대로 객체지향 프로그래밍을 하기 위해 사람을 클래스화 시켜서
User라는 클래스를 만들었는데.. 이렇게 만들어서 얻어지는 장점이라는 것을
이해 하기가 어려웠습니다.
그저 이름과 주민번호를 담고 있는 Value Object 정도의 느낌이죠..

위와 같은 상황도 좀 작게 보면 비슷한 맥락이라고 생각합니다.

User를 만들어서 List에 넣고 contains 비교를 하는데
없다고 나옵니다. 주민번호가 같은 객체임에도 불구하고요..

저런 상황에서 저 객체가 List에 있냐 없냐를 판단하려고 아래와 같은 코드를 쓰기도 합니다.


User가 주민번호와 이름을 가지고 있으므로 저렇게 구현해도 로직은 틀리지 않을 것 입니다.
그래서 객체를 비교해야 하는 곳에서 모두 저렇게 로직을 구현했다고 했을 때...
그럴 일은 없겠지만 먼~~~ 훗날.. "더 정확한 체크를 위해 의료보험번호도 확인해야 한다!" 라는 로직을 추가해야 한다면..
개발자들은 저렇게 구현되어 있는 모든 부분을 찾아서 수정해줘야 합니다.

if문에 &&가 하나 더 들어가겠죠.

사람을 클래스화 시켜서 만들었지만
할 일은 이전과 큰 변화가 없어 보입니다.

이런 상황에서 무엇을 어떻게 해주면
좀 더 그럴듯한 모습으로 User 클래스를 만들 수 있을까요..

JAVA의 모든 Class들은 Object 클래스를 상속합니다.
그리고 이 Object 클래스에서 꼭 알고 넘어가야 하는 메서드가 있습니다.
바로, equals,hashCode,toString,clone 입니다.

다른 메서드는 당장 여기서 얘기 할 것은 아니고..
equals 메서드가 바로 위와 같은 상황에서 사용되어야 할 메서드이고
일반적으로 클래스를 만들 때 반드시 염두해 두어야 할 메서드 입니다.
equals 메서드에 대한 상세한 내용은 이곳을 참고하세요.

User 클래스라면 equals 메서드를 구현하여 사람 객체는 주민번호가 같으면 같은 객체다 라고 정의해야 하고
책이라면 isbn 넘버가 같으면 같은 객체다 라고 정의를 해주어야 합니다.
그래야 객체와 객체의 관계로 프로그램에서 객체를 핸들링 할 수 있기 때문입니다.
객체와 객체의 관계에 대한 정의를 객체가 가지고 있어야지, 다른 클래스에서 그 로직을 구현하고 있으면
그 관계가 변할 때 마다 수정에 들어가는 노력이 적지 않게 됩니다.


equals 메서드를 오버라이드 하였습니다.
(위 equals 메서드의 내용은 Java에서 제공하는 스펙에는 맞지 않습니다. 그냥 이 글의 내용에 맞게 단순화 시켜버린
메서드입니다.)

위와 같이 User 클래스를 수정하고 다시 예제 프로그램을 실행하면
true를 리턴합니다.

의료보험 번호로 체크를 더 해줘야 한다는 로직이 들어가면?

User 클래스만 변경하면 됩니다. User 클래스가 서로 같은지를 판단하는 것은
User 클래스가 가지고 있으면 됩니다. 다른쪽에 영향을 주지 않습니다.

equals나 clone등의 메서드를 구현하지 않아서
생기는 문제는 나중에 찾아내기가 쉽지 않습니다.
특히 clone같은 경우는 구현을 잘못하면 두개의 다른 객체라고 생각되는 넘들이
값을 공유하는 형태가 되어 버리기 때문에
더더욱 그렇습니다.

단순히 bean을 Value Object나 DTO라고 부르는 역할로 사용하려 한다면
모르겠지만, 비지니스를 분석하여 각 영역을 클래스로 뽑아낸 경우라면
위와 같이 equals 메서드를 override해주는 것을 고려하는 것이 좋다고 생각됩니다.

그런게 아니더라도..
java의 다른 클래스들이 다른 클래스들이 이 메서드들을 제대로 오버라이드 하고 있다고 가정하고 있기 때문에
이게 정상적으로 구현이 되어 있지 않으면
제대로 동작하지 않는 클래스들도 생길 것 입니다.

Map 인터페이스를 구현한 클래스들 (HashMap 등) 의 containsKey는 위와 같이 equals 메서드만 재정의 한 상태로는 제대로 작동을 하지 않을 수 있습니다. 원래대로라면 equals 메서드를 재정의 하면 hashCode 메서드도 재정의를 해줘야 합니다.

Posted by 용식
TAG equals, java, OOP
라고 하기까진 뭐하고...
그냥 classloader를 바꿔버리는 것을 이용해서 해보았다.

예전에 실패했을 때의 원인이었던것...
(http://devyongsik.tistory.com/274)

기본적으로 클래스패스에 있던 클래스는
이미 시스템클래스로더가 클래스를 로딩한 상태이기 때문에
그 하위 클래스로더에서 아무리 URLClassLoader를 사용해 바꿔치기 해봤자
영향이 없다는 것...

그렇다면, 방법은..
우리가 hot deploy를 해보고 싶은 클래스를 시스템 클래스로더가 아닌
우리가 정의한 (즉, 우리가 컨트롤 할 수 있는) 클래스로더가 물고 올라가도록 하면
이후에도 우리가 컨트롤 할 수 있지 않을까... 생각했다.

이클립스에서 workspace를 c: 쪽을 사용하고 있는데
일단 이런저런 생각하기가 귀찮아서..
그냥 hot deploy의 대상이 되는 BaseDomain이라는 클래스를
d: 드라이브에 넣고, URLClassLoader를 사용해서 그 클래스를 로딩하도록 구현해봤다.




이렇게 실행하면 d: 에 있는 BaseDomain.class를 물고 올라간다.

이제, 다른 프로젝트던 어디서든..BaseDomain.java의 내용을 바꾸고
컴파일 한 후 위 Main이 돌고 있는 상태에서 새로 컴파일한 class를 D:에 넣어주면
새롭게 컴파일 된 내용으로 콘솔에 내용이 찍힌다.

오호라..~ 이게 되네?

하지만, 이걸 실제 어떻게 구현해서 써보려고 하면..
클래스로더가 하나의 클래스를 로딩하고 있는 상태도 아니고..
클래스가 바뀔 때마다 클래스로더 전체를 내렸다가 다시 생성하고 클래스를 다시 로딩하는 것은
상당히 비효율적이라는 생각이 좀 든다.. .. 아니 비효율이 문제가 아니라 다른 클래스들은 어떻게 되는겨..

그래서 다른 사이트에서 보면
URLClassLoader를 상속받아
새롭게 ClassLoader를 정의해서 .class의 바이트코드를 읽어와
defineClass() 메서드를 사용해서 그 클래스만 다시 로딩하는 방법으로 구현 된 것도 있었다.
(http://tutorials.jenkov.com/java-reflection/dynamic-class-loading-reloading.html)

처음 위 내용 봤을 때는 저렇게까지 해야하나 싶었는데..
생각해보니 안 그럼 낭비가 좀 있을 것 같다...

eclipse4j님이 알려주신 jrebel의 작동 방식도
자기만의 work디렉토리로 모니터링 대상이 되는 클래스들을 모두 옮긴 후
자기가 정의한 클래스로더로 그 클래스들을 전부 로딩한다고 한다. 겉으로 보기엔 상당히 엣지 있는데
속으로는 저런 일을 하고 있었다는 거.... ㅎㅎ

암튼...
세련스럽지는 않지만
해보고 싶은건 결국 해봤다 -_-;;;

근데 난 왜 이걸 그렇게 해보고 싶어했던걸까....-_-ㅋ


Posted by 용식