달력

032010  이전 다음

  •  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  •  
  •  
  •  
처음 Comments in Java Code를 봤을 때는 '뭐 이런 쓸 때 없는 짓을 해보느냐' 싶었다.
다시 마음을 고쳐 먹고, 다시 드려다 보았다. 역시 마음 먹기 나름이다. ^___^

사용자 삽입 이미지

이렇게 코드를 고쳐보았더니 문장(statement) 사이에 주석을 넣으면 안된고 알았던 막연한 스스로의 고정 관념이 깨진다. 고정 관념을 깨고 나면 선택권을 갖게 되기 마련이다. 물론, 이 경우는 대단한 선택권은 아니지만...  
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 영회
닷넷의 CLR(Common Language Runtime)은 단일 플랫폼에서 다수의 언어를 지원하는 것으로 알려져 있었다. 하지만, Novell이 주도하는 mono에 의해서 Linux, FreeBSD, UNIX, Mac OS X, Solaris 등에서도 구동이 가능하게 되었다. 대부분의 플랫폼에 포팅된 것이다.
 
JVM은 다수의 플랫폼에서 단일 언어를 지원하는 것으로 알려져 있었다. 하지만, 이미 2005년 봄을 기준으로 200여개의 언어가 JVM 상에서 구동한다. 이렇게 많은 언어가 JVM에서 구동할 수 있다는 사실에 앞서, 이렇게 많은 언어가 존재한다는 사실부터가 놀랍다. :)
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 영회
BigDecimal을 살펴보면 숫자 계산, 특히 큰 수의 정확한 계산에 최적화 되어 있음을 느낄 수 있다. 생성자의 경우 숫자 데이터를 표현하는 다양한 primitive나 BigInteger 타입을 인자로 받아 생성할 수 있지만, 가장 편리해보이는 것은 문자열(String)을 이용한 객체 생성이다. 다음 코드를 보자.

 assertEquals(new BigDecimal("1100.00100"), new BigDecimal("001100.00100"));
 assertNotSame(new BigDecimal(1100.001), new BigDecimal("001100.00100"));
 assertEquals(5, new BigDecimal("001100.00100").scale());

두번째 행을 보면 숫자 값이 같아도 소수점의 자리수(the number of digits in the fractional part)가 다르면 같지 않은 것으로 인식함을 알 수 있다. 이를 스케일(Scale)이라고 한다. 스케일은 int 타입의 최대값 즉, Integer.MAX_VALUE(2147483647)까지 설정할 수 있다.
new BigDecimal("0").setScale(2147483647);

그러나 0이 아닌 수에 대해서는 부여할 수 있는 스케일이 작아진다. 흥미로운 사실은 어떤 숫자까지 설정할 수 있는지 확인하는 것이 꽤나 번거로운 작업이라는 점이다. Integer.MAX_VALUE 이하의 큰 수를 넣으면 OutOfMemoryError가 발생한다.
 try {
  new BigDecimal("1").setScale(Integer.MAX_VALUE);
 } catch (Exception e) {
  assertEquals(NegativeArraySizeException.class, e.getClass());
 }

BigDecimal의 경우는 반올림이 필요한 경우는 임의로 반올림을 수행하지 않는다. 역시 정확한 계산에 무게를 둔 클래스 설계로 짐작할 수 있다.
 try {
  new BigDecimal("10").divide(new BigDecimal("3"));
 } catch (Exception e) {
  assertEquals(ArithmeticException.class, e.getClass());
 }

디폴트가 없고, 반올림을 어떻게 할 것인지 지정하지 않으면 예외를 발생시킨다.
assertEquals(new BigDecimal(3), new BigDecimal("10").divide(new BigDecimal("3"),
   BigDecimal.ROUND_HALF_DOWN));

BigDecimal이 제공하는 반올림 옵션은 다음과 같다.
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 영회
Tricky instanceof operator는 흥미로운 예제이다. 스터디의 관련 발표에서 민재님은 instanceof 대신 getClass()를 쓸 수 있음을 얘기한 바 있다. 다만, 용도가 다른 것이니까 분명하게 구분할 필요가 있다.

위 그림과 같이 상속이 전제된 상황이라면
class Poly extends Super implements Contract

다음과 같이 하나의 객체가 다수의 타입을 지닐 수 있다.
 Poly poly = new Poly();
 assertTrue(poly instanceof Poly);
 assertTrue(poly instanceof Super);
 assertTrue(poly instanceof Object);
 assertTrue(poly instanceof Contract);

다형성(Polymorphism)은 분명한 장점이지만, 이를 쓰기 위해서는 다층적(layered) 혹은 다차원의(multi-dimensional) 사고를 요구한다.

정확한 클래스를 알고자 할 경우는 getClass()를 활용한다.
 assertEquals(poly.getClass(), Poly.class);
 assertNotSame(poly.getClass(), Super.class);
 assertNotSame(poly.getClass(), Object.class);
 assertNotSame(poly.getClass(), Contract.class);

이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 영회


위 코드의 수행 결과는 어떻게 될까?

1. Call Troubler
2. Execute makeTrouble
3. Catch the Exception
4. do Troubler's some job Finally
6. do some job Finally

5번이 수행되게 하려면 어떻게 하면 될까?
Troubler 객체의 makeTrouble 메소드에서 finally 구문에 위치한 return을 제거하면 된다.
깜찍한 이클립스는 노란 경고등과 메시지를 날려줍니다. :)

finally의 용도는 다음과 같다.


비정상적으로 해당 루틴을 빠져 나가기 전에 마지막으로 할 일이 있을 때 기회를 주는 것이다.

그런데.. 도망을 가버리면 정상적인 상황처럼 루틸을 벗어나 버린다.
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 영회
개발자가 놓치기 쉬운 자바의 기본원리 2절은 이렇게 마무리된다.
즉, null도 object라고 봐야 하지 않을까?

섭섭한 말씀이 아닐 수 없다.
긴 논쟁보다는 한 줄의 시로 섭섭함을 달래보자.

assertFalse(null instanceof Object);

예상대로 파란 불을 볼 수 있다.

사족

이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 영회
TAG NULL
역겨운 Context클래스. Context객체입장에서는 배은망덕(?)하다고 여길지도 모른다.
Servlet/JSP와 EJB 환경에선 Context 객체 없이는 무엇도 하기 힘든데...

나는 Kathy Sierra의 영향을 받아 즉흥적인 그래프를 만든다.
신뢰성 있는 분석을 기반으로 한 그래프는 아니지만 명예훼손의 염려는 없다.

기반이 되는 코드가 아니라 업무 로직을 담은 클래스에 Context가 보인다면 내가 봐도 눈에 거슬릴 것 같다. 나는 대체로 정적 코드가 필요해질 때까지 정적 코드를 사용하지 않는다.라는 지침에 동의한다.

하지만, 편의성을 모토로 하는 유틸리티 메소드/클래스의 상당수는 static이다.

Java EE 환경에서는 컨테이너의 등장으로 인해서 공유의 수준이 다양해졌고, 다양한 수준의 "static"이 등장했는데 그들의 다른 이름이 Context이다.

이제 오후까지 마쳐야 하는 문서 작업 때문에 업무로 복귀해야 한다. 쩝...
역겨운 Context클래스의 링크를 통해 의미의 면적이 좁은이라는 상큼한 글을 만나게 되었다.
이것은 내가 가장 먼저 배운 프로그래밍의 Principle of least privilege에 대한 또다른 해석이다.


이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 영회
오버로딩의 독특한 장점(?)라는 글을 쓴 일이 있는데
함께 공부하는 후배가 이해를 못하는 부분이 있어 글을 씁니다.

타입을 집합으로도 볼 수 있습니다.
집합의 원소는 인스턴스가 되죠.

Class 타입과 String 타입을 집합으로 표현해보죠.

어설프기 짝이 없는 표현이지만
지금 설명하려는 내용에는 충분합니다. :)

모든 집합의 부분집합이 되는 녀석 기억하시나요?
넵.. 수학에 담 쌓지 않으셨던 분들은 아시겠죠.
일단 답을 뒤로 하고 아래 코드를 보죠.


컴파일 에러가 납니다. 모호하다는 것이죠.

타입이 집합이라면 null공집합에 대응됩니다.
개나 소나 다 null을 공집합으로 갖고 있죠. ^^

그렇다면.. 위와 같이 오버로딩을 정의하면..
null은 Class 집합과 String 집합 두 가지 경우를 다 만족시킵니다.
결국 프로그래머는 컴파일러를 난처한 곳으로 몰고 간 것이죠.
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 영회
편리한 것이라고 알고 있었지만...
차츰 부작용이 보고 되고 있다.
레거시[각주:1]를 보다 OO스럽게 포장하려던 계획은 그다지 우아하게 구현되지 못한 듯 하다.


  1. primitive 타입 [본문으로]
이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 영회
In Java 5, 0 is not always equal to 0

흥미로운 내용입니다.
System.out.println(0L == 0);
System.out.println(((Long)0L).equals(0));

결과는
true
false

primitive의 경우 연산전에 int 형 리터럴이 0이 Long형으로 승격되어 비교가 이뤄집니다.
결과적으로 0L == 0 연산 결과는 0L == 0L의 결과와 같죠.

두번째 equals() 메소드 호출을 통한 비교는 어떻게 될까요?
0은 Java5의 autoboxing에 의해서 Integer 형이 될 것이라고 짐작할 수 있습니다.
타입이 다른 객체에 대해서 equals가 동일한 값으로 인지하지 않는 것이죠.

public boolean equals(Object obj) {
if (obj instanceof Long) {
    return value == ((Long)obj).longValue();
}
return false;
}

소스코드를 확인해보면 Long 타입만 비교가 이뤄집니다.

내부적인 메커니즘이 어찌 되었건 타입과 무관하게 API에서는
동일한 0을 제공해준다면 좋겠지만 그렇지 못하네요.

수학의 숫자에 비해 자바에 다양한 숫자 타입이 있다는 점은 주의해야 할 사항입니다.
아직 수학의 숫자에 비해서 자바의 숫자 타입의 추상화 정도에는 차이가 있기 때문이죠.



이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 영회
객체의 상태를 동시에 수정을 하는 것이 허용하지 않을 때 발생시키는 ConcurrentModificationException이 있습니다. ConcurrentModificationException의 쓰임으로 Javadoc API에서 예로 든 것은 하나의 쓰레드가 컬렉션을 순회(iterate) 중일 때, 다른 하나의 쓰레드가 컬렉션을 수정하는 경우입니다. 컬렉션의 모든 요소를 살펴보려고 순회하고 있는데, 컬렉션이 늘었다 줄었다 하는 경우를 생각해보세요. 매우 불안정한 순회가 될 것입니다.

재미있는 생각이 떠올랐습니다. 만일 학교에서 숙제 검사를 하는 경우를 생각해보죠. 요즘도 이런 용어를 쓰는지 모르지만, 1분단부터 차례로 선생님이 학생들의 숙제를 검사하게 됩니다. 만약에 학생들이 자유롭게 자리를 비울 수 있다거나, 검사 도중에 자리를 바꿔 앉을 수 있다면 제대로 된 검사가 불가능할 것입니다. ^^

ConcurrentModificationException은 반드시 다수의 쓰레드가 동반된 상황과 관계된 것은 아니라고 API는 전합니다.
If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.

'숙제 검사'를 제대로 수행하기 위해서는 학생들이 검사를 받는 시점에 자리에 위치해야 합니다. 이것이 숙제검사의 Contract으로 볼 수 있습니다. 위에서 fail-fast 라는 용어가 나오는데요. 이것은 이렇게 설명할 수 있습니다. 학생이 검사를 받기 전에 자리를 비웠다고 해서 반드시 문제가 생기는 것은 아닙니다. 비웠다가도 검사하는 시점에 본인의 위치에 있기만 하면 문제가 될 것이 없습니다. 그러나, 통제의 편의를 위해서는 이러한 문제도 엄격하게 다룰 수 있죠. 이와 같은 정책이 fail-fast 라고 할 수 있습니다.

API에서는 ConcurrentModificationException 구현을 너무 신뢰하지 말라는 듯한 문구가 있습니다.
Fail-fast operations throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.

민재님이 발견한 재미있는 현상이 적절한 예가 아닌가 생각되네요.

public void testArrayList() {
List<String> strings = new ArrayList<String>();
strings.add("1");
strings.add("2");
strings.add("3");
strings.add("4");
strings.add("5");
strings.add("6");
strings.add("7");
strings.add("8");
for (String string : strings)
  if ("7".equals(string))
   strings.remove(string);
}

비교하는 문자열이 7인 경우에만 ConcurrentModificationException가 발생하지 않습니다. 원인을 알아보기 위해서는 ArrayList의 구현을 찾아봐야겠죠. 실제로는 AbstractList의 내부 클래스인 Itr에 구현되어 있습니다.

private class Itr implements Iterator<E> {
...
int expectedModCount = modCount;

modCount는 컬렉션이 수정된 횟수를 임시로 기록하는 값입니다. Iterator를 생성하면서 그 값을 복사해둡니다. 그리고, iterator 객체에서 next() 혹은 remove() 메소드가 호출되면 복사한 값 즉, iterator 생성 시점의 수정 횟수현재의 수정 횟수를 비교합니다. 두 수치가 갖지 않으면, ConcurrentModificationException를 발생시킵니다.

그렇다면 앞의 예제에서 예외가 발생하는 과정을 추정해보고, 왜 "7"을 비교할 때는 예외가 나지 않는지 확인해보죠.
for (String string : strings)
  if ("7".equals(string))
   strings.remove(string);

JDK5에서 지원하는 간결한 for문입니다. foreach 구문이라고도 하죠. 실제로는 strings라는 이름의 ArrayList 객체에의 iterator() 메소드를 호출하게 됩니다. 보다 정확하게 이야기하면 Iteratable 인터페이스를 구현한 객체의 iterator() 메소드를 호출하죠. iterator() 메소드는 Iterator 객체를 반환하는데, hasNext() 메소드를 호출하여 true가 반환되면 next() 메소드를 호출하여 결과를 반환하는 방식으로 컬렉션의 요소들을 순차적으로 반환합니다. 매우 복잡해보이는 일을 foreach 로 표현할 수 있어 편리해진 것이죠.

위의 구문을 다음과 같이 변경할 수도 있습니다. 구문을 더 복잡해지지만, 내부적인 동작을 보여주는데는 더 유리하죠.
String string = null;
for (Iterator it = strings.iterator(); it.hasNext();) {
           string = (String)it.next();
           if ("7".equals(string))
               strings.remove(string);
       }


앞에서 Iterator의 next()ConcurrentModificationException를 발생시킬 수 있다고 했죠. foreach 수행 중에 암묵적으로 next()가 호출된다는 사실을 기억하세요. 위 코드의 remove() 메소드는 Iterator 객체의 것이 아니라 ArrayList의 행위입니다. ArrayList의 remove()에서 아래의 코드를 발견할 수 있습니다.
modCount++;

iterator 생성 시점의 수정 횟수현재의 수정 횟수를 비교합니다!

ConcurrentModificationException이 발생하겠죠.

그렇다면 "7"의 경우는 왜 발생하지 않을까요? 또한, "8"로 비교를 수행해도 의외의 결과를 얻습니다. 소스를 한참 뒤져보고서야 해답은 Iterator 객체의 next()와 hasNext()에 있음을 알게 되었습니다.
public boolean hasNext() {
           return cursor != size();
}

public E next() {
  ...
lastRet = cursor++;


cursor 값은 0부터 시작하여 하나씩 증가하죠. 이를 효과적으로 표현하기 위해서 예제 코드를 수정해보겠습니다.

public void testArrayList() {
List<String> strings = new ArrayList<String>();
strings.add("1");
strings.add("2");
strings.add("3");
strings.add("4");
strings.add("5");
strings.add("6");
strings.add("7");
strings.add("8");
String string = null;
int coursor = 0;
for (Iterator it = strings.iterator(); it.hasNext();) {
  string = (String) it.next();
  System.out.print(++coursor + " : ");
  if ("7".equals(string))
   strings.remove(string);
 
  System.out.println(coursor == strings.size());
}
}

출력 값은 다음과 같습니다.
1 : false
2 : false
3 : false
4 : false
5 : false
6 : false
7 : true

"7"의 경우엔 hasNext()가 false를 반환하게 됩니다. 결국, 그 다음 요소는 순회가 안되고 무시되어 버리죠. ^^

그렇다면 "8"은 어떻습니까? 앞의 예제의 "7"을 "8"로 변경하여 수행해보면, 8번 반복이 아니라 9번 반복이 수행됩니다. 원인은  Iterator 객체의 hasNext() 메소드의 구현이 크기 비교가 아니라 수치가 동일한가를 비교하고 있기 때문이죠. ^^

이러한 현상에 대해 API에서 on a best-effort basis라고 귀뜸해준 것이라면 매우 적절한 처사라 생각됩니다.

Fail-fast operations throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.


이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 영회

final 키워드는 대개 정적인 방식으로 많이 쓰입니다. 예를 들어 클래스 상수로 쓰이거나, 클래스의 상속이나 메소드 재정의(overriding)을 막기 위해서 자주 사용되죠.

상대적으로 잘 쓰이지 않지만, 초기화 이후에 값을 바꿀 수 없는 변수를 만드는 동적인 용도로도 사용이 가능합니다. 자바 언어 레퍼런스를 보면 다음과 같이 내용이 있습니다:

A variable can be declared final. A final variable may only be assigned to once. It is a compile time error if a final variable is assigned to unless it is definitely unassigned immediately prior to the assignment.

C나 C++에 const라는 키워드가 있는데 변수에 대해서는 final보다 const가 더 적절한 이름이라고 생각합니다. 하지만, 클래스나 메소드 정의에 부여하는 경우는 final이 아주 좋다고 생각합니다.

배열을 인자로 받아서 합계를 내는 함수가 있다고 해보죠.

int sum(int[] a) {
  int result = 0;
  for (int i : a)
       result += i;
  return result;
}

이 경우 어떤 이유에서건 아래와 같이 구현된다면 해당 메소드를 사용자 입장에서는 의도하지 않은 결과를 접하게 될 가능성이 높습니다.

int sum(int[] a) {
 int[] others = {-1, -2, -3};
 a = others;
 int result = 0;
 for (int i : a)
  result += i;
 return result;
}

간단한 예를 통해서 보면 완전히 어리섞은 구현이긴 합니다. 하지만, 테스트가 없이 장문의 타이핑을 하는 환경에서는 이와 유사한 일이 일어나지 말라는 법은 없습니다.

int sum(final int[] a) {
  int result = 0;
  for (int i : a)
       result += i;
  return result;
}

이와 같이 정의해주면 적어도 a에 새로운 할당을 시도할 수 없게 됩니다.

3 + 4 = ?

위와 같이 더하기라는 연산을 할 때 피 연산자인 3과 4는 변하지 않습니다. 3과 4가 변한다면 사람들은 위험해서(?) 덧셈을 하지 않겠죠. :)

sum()에 인자로 제공해주는 것은 피연산자와 같습니다. 의미적으로 상수가 전달되어야 하는 경우라고 할 수 있죠. 이럴 때 final을 붙여주면 보다 의미가 명확해질 수 있습니다.

아래와 같은 구문에 왜 final을 쓰면 안되느냐는 질문을 받은 일이 있습니다.

int sum(int[] a) {
  final int result = 0;
  for (int i : a)
       result += i;
  return result;
}

자칫 범하기 쉬운 오류지만, result라는 변수의 용처를 명확하게 정립해보면 논리적인 오류임을 알 수 있습니다. result는 말 그대로 임시 저장소입니다. 지역 변수(local variable)은 임시(temp/temporary) 변수라고 하는데 딱 어울리는 용도로 사용한 것이죠.

세로 타원으로 그린 것이 for 문의 실행시의 논리적인 영역을 나타냅니다. 문맥(context)이라고 할 수도 있겠죠.  반복이 진행되면서 우측으로 이동하기 때문에 배열의 요소들의 이전 값을 기억하기 위해서는 별도의 저장소가 요구됩니다. 어떤 프로그래밍 언어를 배우거나 초기에 익히는 것이지만, 논리적으로 이러한 그림을 머리속에 그릴 수 있다면 더욱 복잡한 경우를 포용할 때 매우 유리합니다.

위와 같은 경우 a[0]의 값이 temp에 들어가 있어야 하고, 반복이 한 차례 더 수행되면 temp 값에 a[1]을 더한 값이 다시 할당되어져야 하니 final로 정의하는 것은 실수죠.




이올린에 북마크하기(0) 이올린에 추천하기(0)
Posted by 영회