Recent Posts

#001 랜섬웨어(Ransomware) 확장자 .ccc 파일 복구 성공~~

약 한달전 지인이 바이러스를 먹었다고 해서 .. 그냥 가져와~~ 윈도우 밀어줄께.. 라고 말했는데..
화면은 본 순간.. 깜짝 놀랬다~~~
랜섬웨어에 걸려서.. 모든 이미지 텍스트 파일들이 암호화가 되어 버렸다.. 젠장..

몇일을 인터넷을 뒤지고 다녔지만 방법을 찾지 못헀는데.. 그 방법이 인터넷에 돌아 다녀.. 방법대로 랜섬웨어 복구를 해보니.. 잘 되었다.
복구 하는 방법은 아래와 같다.
1. 아래 프로그램을 받는다.
ransomware-decrypt.zip

2. 압축을 해제 한다.
압축을 해제하면 TeslaDecoder와 yafu폴더가 있다.

3. TeslaDecoder 폴더를 들어가면 teslaviewer를 실행 시키고 Browse를 클릭하여 랜섬웨어에 걸린 파일을 입력한다.
그럼 아래와 같이 여러가지의 정보가 나온다.
1

4.아래 Create work.txt 버튼을 클릭하여 텍스트 파일을 하나 만든다.

5. TeslaDecoder 파일에 work.txt파일이 생성된다. 여기서 privateKeyBC의 dec부분을 복사한다.
2

6. 압축을 해제한 폴더중 yafu폴더가 있다. 이폴더의 RunYafu.exe파일이 존재 한다. 이 파일을 연다.

7. factorying Threads를 3으로 맞춘다.

8. Tune Yafu Button before factoring의 TuneYafu버튼을 클릭한다.
3

9. 이런창이 생성되면서 5분 이상 자기가 알아서 무언가를 실행한다. 자동으로 창이 닫혀지면 완료가 된것이다.
4

10.yafo폴더에 보면 factoryX86.bat 이나 factoryX64.bat 배치 파일이 존재 한다. 32비트 컴퓨터라면 factoryX86.bat을 실행하고 64비트 컴퓨터라면 factoryX64.bat파일을 실행하면 된다.
<내컴퓨터->마우스 오른쪽-> 속성 을 들어가면 잔신으 시스템 종류를 알수 있다. >
5

11. 그냥 실행하면 파일이 실행된 후 종료 되기 때문에 시작->실행->cmd를 눌러 커맨드 창을 실행한다.

12. 커맨드 창에 factoryX64.bat혹은 factoryx86.bat파일이 있는 경로를 입력하여 실행 한다.
6

13. 조금 전에 work.txt에서 복사한 코드를 커맨드 창에 입력한다. 복사>붙여 넣기 혹은 마우스 오른쪽 클릭
7

14.그리고 엔터를 누르면 쓰레드를 몇개 사용할지 정하는 화면이 나온다. 쓰레드는 1로 정하고 실행한다. 엔터
8

15. ***factors found*** 부분의 값을 복사 한다.(마우스 오른쪽 -> 표시 -> 드레그 -> 마우스 오른쪽): 복사 과정
9

16. 다시 TeslaDecoder 폴더에 보면 TeslaRefactor.exe파일이 있다. 이 파일을 실행 시킨 후

17 커맨드에서 복사한 값을 텍스트에 넣고 work.txt파일에 있는 마지막 부분의 publicKeyFile의 값을 refactor 실행 파일에 public key(hex)부분에 입력한다.
10

18. Find private key를 버튼을 클릭 하게 되면 private key(hex) 값을 복사 한다.
이 값이 실제 복호화 할 수 있는 키이다.
11

19.TeslaDecoder폴더의 TeslaDecoder.exe파일을 실행 시킨후 set key에 복사한 키 값을 입력한다 그리고 확장자를 .ccc가 포함된 확장자를 선택한다.
12

20. Decrypt Folder를 선택하여 복호화 할 폴더(감염된 파일이 있는 폴더)를 선택 하여 복호화를 진행한다.
복호화 진행시 인포 창이 하나 뜨는데 여기서 예를 누르면 암호화된 파일이 복구되면 복구된 파일만 남기고 암호화된 파일을 삭제 한다. 아니요를 누르면 암호화된 파일을 보존한다.
13

21. 로그를 통해 복구가 된 파일이 몇개인지 알수 있다.
14

#023 Solr Cloud 주무르기

이번 장 부터는 솔라 클라우드에 대해서 알아 보려 한다. 주요섹션은 아래와 같다.

1. security - SSL, Logging
2. Scalability - Clustering and Sharding
3. performance - Clustering and Sharding
4. API - HTTP
5. Availability - Replication
6. No loss of data - Replication

이상 끝!!

Solr 메모리 늘리기

솔라의 사용 메모리를 늘리려면 solr.in.sh파일을 수정하면 된다.

vi /etc/default/solr.in.sh 

아래를 내리다 보면
SOLR_HEAP의 값을 원하는 값으로 늘려주면 된다.

SOLR_HEAP="2048m"

그리고 자바의 힙 메모리를 늘려 주려면 SOLR_JAVA_MEM의 옵션을 사용하면 된다.

SOLR_JAVA_MEM="-Xms2048m -Xmx2048m"

끝~!

솔라 5.4.1 설치(자동화 설치)

예전에 솔라는 war파일을 톰켓에 붙여서 사용했지만 이제는 솔라자체에서 제공하는 웹서버를 통해서 구동이 가능하다.

구동하는 방법은 아래와 같다.

1. 솔라 5.4.1버전 파일 받기

 wget http://archive.apache.org/dist/lucene/solr/5.4.1/solr-5.4.1.tgz

2. 압축을 풀기 전 solr압축파일의 권한을 모든권한으로 준다. 그리고 압축 풀기(압축은 자신의 홈에서 푼다.

chmod 777 solr-5.4.1.tgz 
tar -xvf solr-5.4.1.tgz 

3. 이제 설치할 준비가 다 되었다. 설치하기

cd ~/solr5.4.1/bin
sudo bash ./install_solr_service.sh ~/solr-5.4.1.tgz -i /opt -d /var/solr -u solr -s solr -p 9200

굳이 설명을 하자면
솔라 실행 파일은 -i옵션으로 /opt에 설치
솔라 파일 로그 인덱스 파일등은 -d 옵션으로 /var/solr에 설치
솔라 관련 파일 권한은 solr -u으로 조정
솔라 서비스 이름은 solr로 -s옵션으로 지정
솔라 포트는 9200번으로 -p옵션으로 지정

이렇게 설치하면

id: solr: No such user
Creating new user: solr

Extracting /root/solr-5.4.1.tgz to /opt
Installing symlink /opt/solr -> /opt/solr-5.4.1 ...
Installing /etc/init.d/solr script ...
Installing /etc/default/solr.in.sh ...

Waiting up to 30 seconds to see Solr running on port 9200 [/]  
Started Solr server on port 9200 (pid=8510). Happy searching!
  
Found 1 Solr nodes: 

Solr process 8510 running on port 9200
{
  "solr_home":"/var/solr/data",
  "version":"5.4.1 1725212 - jpountz - 2016-01-18 11:51:45",
  "startTime":"2016-02-02T05:07:37.159Z",
  "uptime":"0 days, 0 hours, 0 minutes, 10 seconds",
  "memory":"56.5 MB (%11.5) of 490.7 MB"}

Service solr installed.

솔라 서비스 까지 설치 되었다.

서비스를 죽이거나 살릴려면

service solr restart/start/stop

로 조정 하면 된다.

끝.

#013 예외(Exceptions)

이 장에서는 효과적으로 예외를 사용하는 지침을 제공한다.


예외 상황에서만 예외를 사용하자

/ 예외의 터무니 없는 사용. 절대 이렇게 하지 말자!
try {
    int i = 0;
    while(true)
        range[i++].climb();
} 
catch(ArrayIndexOutOfBoundsException e) {
}

위의 코드는 예외를 이용하여 while 루프문을 종료시키고 있다.
배열의 범위를 벗어나는 최초의 배열 요소를 사용하려는 순간, ArrayIndexOutOfBoundsException 예외가 발생하고(throw), 검출되고(catch), 무시되면서 이 무한 루프는 종료된다.
이런 식의 코드는 본래의 목적을 혼란스럽게 하고 성능을 저하시키며, 코드가 제대로 동작하는 것을 보장하지 못한다.
코드의 취지와 무관한 버그가 생겨 우리도 모르는 사이에 루프 실행이 실패하고 버그를 감추게 되어 디버깅이 무척 복잡해진다.
예외는 예외적인 상황에서 사용하기 위해 설계된 것이니, 정상적인 흐름 제어에 예외를 사용하지 말자.


복구 가능 상황에는 checked 예외를 사용하고 런타임 예외는 프로그램 에러에 사용하자

자바에서는 던질 수 있는(throwable) 세 종류의 예외를 제공한다.

1. checked 예외
명시적으로 try-catch-finally 예외 처리를 해야하는 것
2. runtime 예외
JVM이 정상적으로 작동하는 동안에 발생하는 예외
3. error
프로그램이 catch 해서는 안되는 심각한 문제

언제 어떤 예외를 사용해야하는 것이 적합한지에 대해 프로그래머들 간에 혼선이 있을 수 있으나, 몇 가지 일반적인 규칙이 있다.
메소드 호출자가 예외 복구를 할 수 있는 상황에서는 checked 예외를 사용하자
unchecked 예외에는 runtime 예외와 error가 있으며, 이들은 catch할 필요가 없고 일반적으로는 catch해서도 안된다.
unchecked 예외나 에러가 발생했다는 것은 복구 불가능하고 계속 실행해봐야 더 해롭기만 한 상황이라는 것을 의미한다.

만일 프로그램에서 catch하지 않으면 그 예외에 적합한 에러 메세지가 출력되면서 현재 실행 중인 Thread가 중단된다.
프로그래밍 에러는 runtime 예외를 사용하자.
대부분의 runtime 예외는 API를 사용하는 client가 그 API의 명세에 설정된 규약을 지키지 않은 것을 말한다.

예를 들면, 배열의 인덱스 값은 0 부터 (배열길이 – 1) 사이의 값이어야 한다는 것이 배열 사용할 때의 규약이다.
ArrayIndexOutOfBoundsException 예외는 이러한 규약을 위반했다는 것을 나타낸다.
자바 언어 명세(JLS)를 보면 error는 JVM에서 사용하며, 자원 부족, 불변 규칙 위반에 따른 실패, JVM이 실행을 계속할 수 없는 상황 등을 나타낸다고 되어 있다.

이런 내용에 의거하여, 에러의 최상위 클래스인 Error를 상속받는 서브 클래스는 만들지 않는 것이 좋다.
우리가 구현하는 모든 unckecked 예외는 RuntimeException의 서브 클래스여야 한다.


checked 예외의 불필요한 사용을 피하자

checked 예외는 프로그래머가 예외 상황을 처리하지 않을 수 없도록 한다. 그런 점에서 checked 예외를 과용하면 API 사용을 불편하게 만들 수 있다.
어떤 메소드가 하나 이상의 checked 예외를 던진다면, 그 메소드를 호출한 코드에서는 하나 이상의 catch 블록에서 예외를 처리하거나,

public void DynamicDataTest() {
 
    try {
        URL url = new URL("http://www.jabook.org/DynamicData.class");
        InputStream is = url.openStream();
        FileOutputStream fos = new FileOutputStream("DynamicData.class");
        int i;
        while((i = is.read()) != -1) {
            fos.write(i);
            System.out.println("|");
        }
        fos.close();
        is.close();
        Class c = Class.forName("DynamicData"); 
        Object obj = c.newInstance(); 
        System.out.println(obj);
    }
    catch ( IOException e ) {
        e.printStackTrace();
    }
    catch ( ClassNotFoundException e ) {
        e.printStackTrace();
    }
    catch ( InstantiationException e ) {
        e.printStackTrace();
    }
    catch ( IllegalAccessException e ) {
        e.printStackTrace();
    }
}

또는 예외를 던지는 메소드를 호출한 메소드가 자신의 메소드 선언부에 throws 키워드를 사용해서 그 예외들을 선언함으로써, 외부로 예외 처리를 넘겨야 한다.

public void DynamicDataTest() {
    throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
 
    URL url = new URL("http://www.jabook.org/DynamicData.class");
    InputStream is = url.openStream();
    FileOutputStream fos = new FileOutputStream("DynamicData.class");
    int i;
    while((i = is.read()) != -1) {
        fos.write(i);
        System.out.println("|");
    }
    fos.close();
    is.close();
    Class c = Class.forName("DynamicData"); 
    Object obj = c.newInstance(); 
    System.out.println(obj);
}

코드출처: http://www.jabook.com/jabook2/bs/bsTreeLoad.do?ba_no=72

11장 자바 Reflection > 11.1 Class 클래스 > 11.1.4 동적 바인딩 클래스 I둘 중 어떤 방법을 사용하건 프로그래머에게 막중한 부담을 준다.

첫번째 방법은 catch 블록에서 예외처리를 하는 내용이 없는데도 checked 예외를 사용함으로서 할 일이 많아진다.
만일 API를 올바르게 사용해도 예외 사항을 피할 수 없고, 예외를 만나더라도 그 API를 사용하는 프로그래머가 어떤 조치를 취해야 한다면, unchecked 예외를 사용하는 것이 더 적합하다.
checked 예외를 unchecked 예외로 바꾸는 한 가지 방법은, 해당 예외를 발생시키는 메소드를 두 개의 메소드로 쪼개는 것이다.
쪼개진 두개의 메소드 중, 하나는 예외 발생 여부를 나타내는 boolean 값을 반환하게 된다.

아래의 코드를,

// checked 예외를 사용한 메소드 호출
try {
    obj.action(args);
}
catch ( TheCheckedException e ) {
    ... // 예외 처리
}

이런 형태로 바꾼다.

// 상태 검사 메소드와 unchecked 예외를 사용한 메소드 호출
if ( obj.actionPermitted(args) ) {
    obj.action(args);
} else {
    ... // 예외 처리
}

변환된 코드가 전보다 깔끔하지 않지만, 유연성은 더 좋다.
그러나 이 메소드가 호출된 객체가 동시적으로 사용되거나(thread 등에서), 외부에서 객체의 상태를 변경해야 한다면 이 코드는 부적합하다.
actionPermitted 메소드와 action 메소드가 호출되는 시점 사이에 그 객체의 상태가 변경될 수 있기 때문이다.


표준 예외를 사용하자

기존에 정의된 예외를 재사용할 때의 장점

1. 프로그래머들이 이미 익숙해진 내용이므로, 배우고 사용하기 쉽다.
2. 생소한 예외를 사용하지 않으므로 코드를 이해하기 쉽다.
3. 적은 수의 예외 클래스를 사용하므로, 메모리 사용도 적게하고 클래스를 메모리로 로딩하는 시간도 줄어든다.
 IllegalArgumentException: null이 아닌 매개 변수 값이 부적합할 때
 IllegalStateException: 객체가 메소드 호출이 가능한 상태가 아닐 때
 NullPointerException: 매개 변수 값이 null일 때
 IndexOutOfBoundsException: index 매개 변수 값이 범위를 벗어날 때
 ConcurrentModificationException: 동시적인 수정이 금지된 객체가 변경되었을 대
 UnsupportedOperationException: 해당 객체에서 메소드를 지원하지 않을 때

재사용할 예외를 선택하는 것은 정확한 과학과는 다르므로, 엄격한 규칙은 없다.


하위 계층의 예외 처리를 신중하게 하자

어떤 메소드에서 자신이 수행하는 작업과 뚜렷한 관계가 없는 예외가 발생한다면 혼란스러울 것이다.
이런 일은 하위 계층의 추상체에서 발생한 예외를 메소드가 자신을 호출한 메소드로 throw할 때 종종 발생한다.

하위 계층의 추상체: 예를 들어, 네트워크나 DB와 같이 외부와의 인터페이스를 담당하는 하위 계층 클래스 등이 해당된다.

이런 문제를 방지하려면, 상위 계층에서 하위 계층의 예외를 반드시 catch 해야 한다.
그리고 하위 계층에서 발생한 예외 대신, 상위 계층에서 알수 있는 예외로 바꿔 던져야 한다.
다음과 같은 이디엄을 예외 변환(exception translation)이라고 한다.

// 예외 변환
try {
    ... // 하위 계층의 추상체를 사용하여 작업하는 코드
} 
catch(LowerLevelException e) {
    throw new HigherLevelException(...);
}

다음은 List 인터페이스의 골격 구현(skeletal implementation)이라고 할 수 있는 AbstractSequentialList의 예외 변환 코드 예이다.

public abstract class AbstractSequentialList<E> extends AbstractList<E> {
    /**
     * Returns the element at the specified position in this list.
     *
     * <p>This implementation first gets a list iterator pointing to the
     * indexed element (with <tt>listIterator(index)</tt>).  Then, it gets
     * the element using <tt>ListIterator.next</tt> and returns it.
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        try {
            return listIterator(index).next();
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }
}

예외 변환의 특별한 예로 예외 연쇄(exception chaining)가 있는데, 이것은 고수준(상위 계층) 예외를 유발시킨 저수준(하위 계층) 예외가 디버깅에 도움이 될 경우에 적합하다.
즉, 예외를 유발시킨 근원인 저수준 예외가 고수준 예외로 전달되는 것으로써, 이때 고수준 예외에서는 저수준 예외를 가져오는 접근자 메소드(Throwable.getCause)를 제공한다.

// 예외 연쇄
try {
    ... // 하위 계층의 추상체를 사용하여 작업하는 코드
} 
catch(LowerLevelException cause) {
    throw new HigherLevelException(cause);
}

최상위 예외 클래스는 Throwable로써, 이 클래스의 생성자에는 예외 연쇄를 알수 있는 생성자가 존재한다.

public class Throwable implements Serializable {
    // 생성자
    public Throwable(Throwable cause) {
        fillInStackTrace();
        detailMessage = (cause==null ? null : cause.toString());
        this.cause = cause;
    }
 
    public Throwable getCause() {
        return (cause==this ? null : cause);
    }
 
    public synchronized Throwable initCause(Throwable cause) {
        if (this.cause != this)
            throw new IllegalStateException("Can't overwrite cause");
        if (cause == this)
            throw new IllegalArgumentException("Self-causation not permitted");
        this.cause = cause;
        return this;
    }
}

따라서 다음과 같이 수퍼 클래스의 생성자를 호출하면 된다.

// 예외 연쇄를 사용하는 고수준 예외 클래스
class HigherLevelException extends Exception { // 우리가 예외 클래스를 정의할 때는 일반적으로 Exception 클래스를 상속 받는다.
    HigherLevelException(Throwable cause) {
        super(cause);
    }
}

대부분의 표준 예외들은 이러한 예외 연쇄를 알고 있는 생성자를 갖고 있으나, 그런 생성자가 없을 경우에는 Throwable.initCause 메소드를 사용하여 근원 예외를 설정할 수 있다.
이렇게 예외 연쇄를 이용하면, 프로그램에서 근원 예외를 사용할 수 있는 것은 물론이고, 근원 예외의 스택 추적정보를 고수준 예외의 것과 통합할 수 있다.
하위 계층에서 발생한 예외를 분별없이 전달하는 것보다는 예외 변환을 사용하는 것이 좋지만 남용해서는 안된다.
하위 계층에서 발생한 예외를 처리하는 가장 좋은 방법은,
상위 계층 메소드의 매개 변수를 하위 계층 메소드로 전달할 때, 유효성을 철저하게 검사하여 예외가 생기지 않도록 하는 것이다.
하위 계층에서 발생하는 예외를 막을 수 없을 때 좋은 방법은,
상위 계층에서 java.util.logging과 같은 로깅 기능을 이용하여, 하위 계층 메소드에서 발생한 예외를 조용히 처리하는 것이다.
이렇게 하면, 클라이언트 코드와 최종 사용자는 문제에서 격리되고 시스템 관리자가 문제를 조사할 수 있다.


메소드가 던지는 모든 예외를 문서화하자

Javadoc의 @throws 태그를 사용하여 항상 checked 예외는 별도로 선언하고, 각 예외가 발생하는 상황을 정확하게 문서화하자.
메소드가 던지는 예외가 많다고 “throws Exception”으로 한다거나, 더 나쁘게는 “throws Throwable”과 같은 식으로, 예외 클래스의 수퍼 클래스로 함축해서 나타내면 안된다.
이렇게 하면 메소드 사용자가 그 메소드가 던질 수 있는 예외에 대해서 제대로 알 수 없게 된다.
Javadoc의 @throws 태그를 사용하여 메소드가 던질 수 있는 unchecked 예외를 문서화하자. 그러나 메소드 선언부의 throws 키워드에는 unchecked 예외를 넣지 말자.
어떤 것이 checked 예외이고, unchecked 예외인지 프로그래머가 아는 것이 중요하다. 두 가지 예외에 따라 해야할 일이 다르기 때문이다.
만일 같은 클래스 내의 여러 메소드에서 동일한 이유로 어떤 한 가지 예외를 던진다면, 그 예외를 메소드에 개별적으로 문서화하기보다, 클래스 문서화 주석에 한 번만 작성하는 것이 좋다.


실패 상황 정보를 상세 메시지에 포함하자

catch 하지 않은 예외로 인해 프로그램 실행이 실패하면, 시스템에서 자동으로 그 예외의 스택 추적정보(stack trace)를 출력한다.
스택 추적정보에는 그 예외를 나타내는 문자열(string representation)이 포함되는데, 이 문자열은 그 예외의 toString 메소드가 호출되어 만들어진 것이다.
일반적으로 문자열은 예외 클래스 이름 다음에 상세 메세지(detail message)가 연결된 형태로 구성된다.
그 예외의 상세 메시지는 향후 분석을 위해 필요한 실패 상황 정보를 담고 있어야 한다.
만일 소프트웨어가 실패했을 때, 프로그래머는 그 상세 메세지를 바탕으로 실패 사항을 쉽게 재현할 수 있을 것이다.
실패 상황 정보를 잡으려면, “예외 발생에 기여한” 모든 매개 변수와 필드의 값이 예외의 상세 메세지에 포함되어야 한다.
적절한 실패 상황 정보가 예외의 상세 메시지에 포함되도록 하는 한 가지 방법은,
해당 예외 클래스의 생성자에 상세 문자열을 인자로 받는 대신 실제 필요한 상황 정보를 요청하여 만드는 것이다.
IndexOutOfBoundsException 예외 클래스의 예를 들자면, IndexOutOfBoundsException(String s)처럼 String 인자를 받고 있는 생성자보다

package java.lang;
 
/**
 * Thrown to indicate that an index of some sort (such as to an array, to a
 * string, or to a vector) is out of range. 
 * <p>
 * Applications can subclass this class to indicate similar exceptions. 
 *
 * @author  Frank Yellin
 * @version %I%, %G%
 * @since   JDK1.0
 */
public
class IndexOutOfBoundsException extends RuntimeException {
    /**
     * Constructs an <code>IndexOutOfBoundsException</code> with no 
     * detail message. 
     */
    public IndexOutOfBoundsException() {
	super();
    }
 
    /**
     * Constructs an <code>IndexOutOfBoundsException</code> with the 
     * specified detail message. 
     *
     * @param   s   the detail message.
     */
    public IndexOutOfBoundsException(String s) {
	super(s);
    }
}

다음과 같이, 실패 상황정보를 상세 메시지로 전달하는 이디엄을 사용하는 것을 적극 권장한다.

/**
 * IndexOutOfBoundsException 상황 정보 구성
 *
 * @param   lowerBound 인덱스의 최저값
 * @param   upperBound 인덱스의 최고값
 * @param   index 실제 인덱스 값
 */
public IndexOutOfBoundsException(int lowerBound, int upperBound, int index) {
    // 실패 상황정보를 담은 상세 메세지를 만든다.
    super("Lower Bound" + lowerBound + ", Upper Bound" + upperBound + ", Index" + index);
 
    // 프로그램에서 사용하기 위해 상황 정보를 보존한다.
    this.lowerBound = lowerBound;
    this.upperBound = upperBound;
    this.index= index;
}

항목 58에서 제안했듯이, 실패 상황 정보를 알려주는 접근자 메소드를 해당 예외에서 제공하는 것이 좋을 수도 있다.
특히 unchecked 예외보다 checked 예외는 그 접근자 메소드를 사용하여 유용하게 장애를 복구할 수 있다.
그러나 unchecked 예외일지라도 일반 원칙(항목 10)에 따라 접근자 메소드를 제공하는 것이 바람직 하다고 생각한다.


실패 원자성을 갖도록 노력하자

일반적으로, 호출된 메소드가 실행에 실패하더라도 객체 상태는 메소드 호출 전과 같아야 한다. 이런 특성을 갖는 메소드를 실패 원자성(failure atomic) 메소드라 한다.
이런 효과를 얻는 방법 몇 가지가 있다.

1. 가장 간단한 방법은 불변 객체로 설계하는 것이다 (항목 15).

객체가 변하지 않으므로 실패 원자성을 고려하지 않아도 된다.
가변 객체를 처리하는 메소드의 경우 실패 원자성을 성취하는 가장 보편적인 방법은, 연산 수행 전에 매개 변수의 유효성을 검사하는 것이다 (항목 38).
이렇게 하면 객체의 변경이 시작되기 전에 미리 예외를 던질 수 있다.
2. 실패 원자성을 성취하는 더 좋은 방법은, 객체를 변경하는 코드에 앞서 실패할 수 있는 코드가 먼저 실행되도록 연산 순서를 조정하는 것이다.
3. 흔하지 않은 방법이지만, 연산 도중에 발생하는 실패를 가로채는 복구 코드(recovery code)를 작성하는 것이다.
이 방법은 주로 영속성(디스크 기반의)을 갖는 데이터 구조에 사용된다.
4. 객체의 임시 복사본을 만들어 연산을 수행하고, 연산 작업이 성공하면 그 객체를 임시 복사본으로 변경하는 것이다.
일반적으로 실패 원자성은 바람직한 것이지만, 복구 불가능한 에러가 발생 했을 때는 실패 원자성을 유지하기 위한 시도조차 할 필요가 없다.
비용이나 복잡도를 현저하게 증가시키는 연산일 경우, 실패 원자성이 바람직하지 않을 수도 있다.

예외를 묵살하지 말자

// 비어있는 catch 블록은 예외를 묵살한다 - 의도가 의심스럽다!
try {
    ...
} 
catch(SomeException e) {
}

unchecked와 checked 예외 모두, catch 블록을 비워 예외를 묵살하지 말자.
우리도 모르는 사이에 계속해서 에러에 직면하는 프로그램을 초래하게 된다.
예외를 무시해도 되는 때는 FileInputStream을 닫을 때이지만, 이런 경우일지라도 예외를 처리하는 것이 좋다.

creative by javacafe

#012 메소드(method)

이 장에서는 매개 변수와 return 값을 처리하는 방법, 메소드 시그니처(signature)를 설계하는 방법, 메소드를 문서화하는 방법에 대해서 설명한다.


매개 변수가 유효한지 검사하자

대부분의 메소드와 생성자는 자신들의 매개변수로 전달될 수 있는 값에 제한을 둔다.
예를 들면, 배열의 인덱스는 음수가 아니어야 하고 객체 참조는 null이 아니어야 한다는 것들이다.
이런 모든 제약은 명확하게 문서화해야 하며, 메소드 몸체 코드의 맨 앞에서 검사하도록 해야한다.
public 메소드의 경우는 Javadoc의 @throws 태그를 사용해서 매개 변수의 값의 제약을 위반했을 때 발생되는 예외를 문서화 한다(항목 62).
일반적으로 IllegalArgumentException, IndexOutOfBoundsException, NullPointerException 예외가 될 것이다 (항목 60).

// BigInteger.java 일부 발췌
 
    /**
     *  그 값이 (this mod m)인 BigInteger를 반환한다.
     * 이 메소드는 항상 양수의 BigInteger를 반환하는
     * BigInteger의 remainder 메소드와는 다르다.
     *
     * @param  m은 계수(modulus)로서, 반드시 양수여야 한다.
     * @return this mod m
     * @throws ArithmeticException 만일 m이 0보다 작거나 같으면
     */
    public BigInteger mod(BigInteger m) {
	if (m.signum <= 0)
	    throw new ArithmeticException("BigInteger: modulus not positive");
 
	BigInteger result = this.remainder(m);
	return (result.signum >= 0 ? result : result.add(m));
    }

필요하면 방어 복사본을 만들자
자바는 안전한 언어(safe language)이기 때문에 메모리 훼손 에러로부터 자유롭다.
그러나 안전한 언어일지라도, 노력 없이 다른 클래스와 독립적인 클래스를 만드는 것은 어려운 일이다.
우리는 클라이언트가 불변 규칙을 파괴할 수도 있다라는 가정하에 방어적으로 프로그램을 작성해야 한다.

 
// 결함이 있는 "불변" Period 클래스
 
import java.util.*;
 
public final class Period {
    private final Date start;
    private final Date end;
 
    /**
     * @param  start 시작일
     * @param  end 종료일, 시작일보다 빠르면 안된다.
     * @throws IllegalArgumentException 시작일이 종료일보다 늦으면 발생
     * @throws NullPointerException 시작일이나 종료일이 null이면 발생
     */
    public Period(Date start, Date end) {
        if (start.compareTo(end) > 0)
            throw new IllegalArgumentException(
                start + " after " + end);
        this.start = start;
        this.end   = end;
    }
 
    public Date start() {
        return start;
    }
    public Date end() {
        return end;
    }
 
    public String toString() {
        return start + " - " + end;
    }
 
    // 나머지 코드 생략
}

Period 클래스를 얼핏보면 불변이면서 시작일이 종료일보다 늦지 않는다는 불변 규칙을 지키도록 하는 것 같이 보인다.
그러나 Date가 가변 객체라는 것을 이용해서 쉽게 불변 규칙을 깰 수 있다.

import java.util.*;
 
public class Attack {
    public static void main(String[] args) {
        // 첫번째 공격 - Period 인스턴스의 내부를 공격한다.
        Date start = new Date();
        Date end = new Date();
        Period p = new Period(start, end);
        end.setYear(78);  // p의 내부를 변경한다!
        System.out.println(p);
    }
}

위와 같은 공격으로 부터 Period 인스턴스 내부를 보호하려면, 가변 객체인 매개 변수의 방어 복사본(defecsive copy)을 만들어서 생성자에 전달해야 한다.

// 수정된 생성자 - 매개 변수의 방어 복사본을 만든다.
public Period(Date start, Date end) {
       this.start = new Date(start.getTime());
       this.end   = new Date(end.getTime());
 
       if (this.start.compareTo(this.end) > 0)
              throw new IllegalArgumentException(start +" after "+ end);
}

방어 복사본은 매개 변수의 유효성 검사에 앞서 만들어야 하며(항목 38), 유효성 검사는 원본이 아닌 복사본을 대상으로 한다는 것에 유의하자.
매개 변수가 검사되는 시간과 복제되는 시간 사이에 다른 쓰레드가 매개 변수를 변경하는 것으로부터 클래스를 보호해준다.
위에서 교체한 생성자가 공격을 방어한다고 해도 Period 인스턴스가 변경될 가능성은 여전히 있다.

import java.util.*;
 
public class Attack {
    public static void main(String[] args) {
        // 두번째 공격 - Period 인스턴스의 내부를 공격한다.
        Date start = new Date();
        Date end = new Date();
        Period p = new Period(start, end);
        p.end().setYear(78);  // p의 내부를 변경한다!
        System.out.println(p);
    }
}

Period의 접근자(accessor) 메소드에서 자신의 내부 가변 필드들에 대한 접근을 제공하고 있기 때문이다.
두번째 공격을 방어하려면 접근자 메소드를 수정하여 내부 가변 필드의 방어 복사본을 반환하도록 하면 된다.

// 수정된 접근자 메소드 - 내부 필드의 방어 복사본을 만든다.
public Date start() {
    return new Date(start.getTime());
}
 
public Date end() {
    return new Date(end.getTime());
}

매개 변수의 방어 복사는 불변 클래스를 위한 것만은 아니다.
클라이언트가 제공하는 객체를 내부 데이터 구조로 넣는 메소드나 생성자를 작성할 때는 언제든지 해당되므로, 이 경우 제공하는 객체가 가변적이 될 수 있는지 잘 생각하자.
내부 컴포넌트를 클라이언트에게 반환할 때에도 방어 복사를 해야한다.
우리 클래스가 불변이건 아니건, 가변적일 수 있는 내부 컴포넌트의 객체 참조를 반환할 때는 방어 복사를 반환해야 한다.
그러나 방어 복사 비용이 엄청나게 비싸면서, 클라이언트가 컴포넌트를 변경하지 않을 것이라는 신뢰가 있으면 방어 복사를 하지 않아도 된다.


메소드 시그니처를 신중하게 설계하자
이 항목은 낱개로 논할 정도의 가치가 없는 API 설계 힌트들을 모아놓은 것이므로 간단하게 훓고 넘어가자.

메소드 이름을 신중하게 짓자.
이름은 항상 표준 작명 규칙(standard naming convention)을 따라야 한다 (항목 56).
편리한 메소드를 만드는 것에 너무 열중하지 말자.
메소드가 너무 많으면, 클래스를 배우고 사용하고 문서화하고 테스트하고 유지하기가 어렵게 된다.
너무 많은 매개 변수를 피하자. 매개변수는 4개 이하를 목표로 하자.
긴 매개변수를 줄이는 방법
1. 하나의 메소드를 여러 개로 쪼갠다.
2. 매개 변수 그룹들을 보전하는 지원 클래스(helper class)를 만든다.
   일반적으로 지원 클래스들은 static 멤버 클래스이다 (항목 22).
3. 객체 구축에서부터 메소드 호출까지 빌더 패턴(항목 2)을 적용한다.
매개 변수 타입은 클래스보다 인터페이스를 사용하자 (항목 52).
boolean 매개 변수 보다는 2개의 요소를 갖는 enum을 사용하자.
읽기와 작성이 더 쉬운 코드를 작성할 수 있다.
public enum TemperatureScale { FAHRENHEIT, CELSIUS} // 화씨, 섭씨

Thermometer.newInstance(true) 보다는 Thermometer.newInstance(TemperatureScale.CELSIUS)가 더 좋다.
향후 버전에서는 Thermometer에 새로운 static 팩토리 메소드를 추가하지 않고도 KELVIN을 TemperatureScale 에 추가할 수 있다.
또 온도 눈금(temperature scale)간의 관계를 enum 메소드로 만들 수 있다 (항목 30).
예를 들어 각 온도 눈금 상수는 double 타입의 값을 매개변수로 받아 섭씨(Celsious) 온도로 맞추는 메소드를 가질 수 있다.


오버로딩(overloading)을 분별력 있게 사용하자

classify 메소드의 인자에 따라 컬렉션을 분류하는 다음 프로그램을 보자.

// 결함이 있다! - 이 프로그램은 무엇을 출력할까?
 
import java.util.*;
import java.math.*;
 
public class CollectionClassifier {
    public static String classify(Set<?> s) {
        return "Set";
    }
 
    public static String classify(List<?> lst) {
        return "List";
    }
 
    public static String classify(Collection<?> c) {
        return "Unknown Collection";
    }
 
    public static void main(String[] args) {
        Collection<?>[] collections = {
            new HashSet<String>(),
            new ArrayList<BigInteger>(),
            new HashMap<String, String>().values()
        };
 
        for (Collection<?> c : collections)
            System.out.println(classify(c));
    }
}
 
---------- execute ----------
Unknown Collection
Unknown Collection
Unknown Collection

이 프로그램을 실행하면 Set List Unknown Collection 순으로 출력될 것 같지만 그렇지 않다.
왜냐하면, classify 메소드가 오버로딩되어서, 호출될 메소드가 컴파일 시점에 결정되기 때문이다.
런타임 타입은 각각 다르지만, 컴파일 시 타입이 동일하게 Collection c이므로 Unknown Collection이 세번 출력되는 것이다.

creative by javacafe

#011 열거형(Enum)과 주석(Annotation) 두번째

작명 패턴보다는 주석(annotation)을 사용하자

What is the Annotation?
어노테이션은 일종의 메타 정보를 컴파일러에게 제공할 수 있는 문법이다.
// 나 /**/ 등으로 적는 문법은 컴파일러는 알아먹을 수 없고 사람, 정확히는 프로그래머에게 이 코드는 이러저러한 코드다…라고 정보를 주는 문법이지만
어노테이션은 컴파일러에게 메타 정보를 주는 문법이다.
물론 잘 쓴 어노테이션은 프로그래머에게도 정보를 줄 수 있다.


참고 ## 메타 정보

메타데이터 (metadata)란 데이터(data)를 위한 데이터다. 어떤 데이터 즉 구조화된 정보를 분석, 분류하고 부가적 정보를 추가하기 위해 그 데이터 뒤에 함께 따라가는 정보를 말한다.
이를테면, 디지털 카메라에서는 사진을 찍어 기록할 때마다 카메라 자체의 정보와 촬영 당시의 시간, 노출, 플래시 사용 여부, 해상도, 사진 크기 등의 사진 정보를 화상 데이터와 같이 저장하게 되어 있다. 이러한 데이터를 분석하여 이용하면 그 뒤에 사진을 적절하게 정리하거나 다시 가공할 때에 아주 유용하게 쓸 수 있는 정보가 된다.

어노테이션 문법
아마 일반적으로 제일 많이 볼 수 있는 어노테이션은 @Override 일것이다.

@Override
public void xxx() { ... }

어노테이션의 선언 방법부터 살펴보자.
어노테이션은 @interface 타입으로 선언하며 뒤에 이름이 온다. 만일 TODO 라는 작업이라는 메타 정보를 제공하는 어노테이션을 선언한다면, 이러한 문법이 된다.

public @interface TODO {
  String value();
}

만일 선언한 어노테이션을 쓴다면 이러한 형식이 될 것이다.

@TODO("유대리 빨리 구현해줘요")
public class Something { ... }

위의 String value() 는 어노테이션 안에 문자열을 인자로 줄 수 있다는 것을 의미한다. 어노테이션도 객체 타입이기에 상태, 즉 값을 가질 수 있다.
어노테이션에는 이 어노테이션이 선언될 수 있는 요소(클래스, 메서드, 필드, 생성자) 를 지정하거나 주석의 생존 스코프(소스, 클래스, 런타임)를 지정해줄 수 있다.
만일 위 주석을 런타임 단계에도 적용하고, 메소드 레벨에만 적용하고 싶다면 어노테이션 선언시 다음과 같은 메타 어노테이션을 선언하면 된다.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TODO {
  String value();
}

@Retention 은 어노테이션이 얼마나 오랫동안 유지되는지에 대해 표시해줄 수 있고, RetentionPolicy enum으로 총 3가지를 가질 수 있으며 SOURCE, CLASS, RUNTIME 이 있다.
위 어노테이션은 RUNTIME 선언이므로, 런타임 시에도 생존하는 스코프를 지닌다. 이런 생존범위라면 각종 런타임 레벨에서 어노테이션의 값을 조회하고 프로그래밍 적으로 이용할 수 있다.
@Target으로는 이 어노테이션이 적용될 요소를 지정해줄 수 있다. 이 어노테이션은 메서드 레벨에만 적용된다.


뭐가 좋지?
만일 특정 DB의 값을 쿼리하여 매핑하는 자바 DTO JavaBeans 가 있다고 해보자.

public class User {
 
    String name;
    String password;
    int todayAccess:
 
    public String getName() { return this.name; }
    public String setName(String name) { this.name = name; }
 
    public String getPassword() { return this.password; }
    public String setPassword(String password) { this.password = password ; }
 
    public String incAccess() { this.todayAccess++; }
    public String getAccessCount() { return this.todayAccess; }
 
}

이러한 빈즈는 자바빈즈 규약을 따른 작명 규칙에 의해 만들어져 있다.
필드 하나만 get/get이 없는데 이 필드는 DB에 있는 데이터가 아닌 프로그램 실행 중 동적으로 변화하는 값이라고 가정한다.
여기까진 문제될 게 없다. 하지만 저런 DB 매핑 DTO 클래스가 많아지면 자연스럽게 클래스 수는 많아지고, DB 매핑 클래스를 프로그램에서 체크하여 한번에 관리하고 싶을 경우, 일일히 그 패키지를 기억하여 관리하는 방법은 너무 번거롭고 실수하기 쉽다.
어떻게 하면 좋을까…위의 클래스에 어노테이션을 적용하여 의미를 분명하게 지정해 본다면 어떨까?
아래와 같이 지정해보자. 먼저 DB 매핑 클래스라는것을 명시하는 어노테이션을 선언한다.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Entity{
  String value();
}

그 다음에는 저 어노테이션을 지정한다.

@Entity("user_table")
public class User {
 
    String name;
    String password;
    int todayAccess:
 
    public String getName() { return this.name; }
    public String setName(String name) { this.name = name; }
 
    public String getPassword() { return this.password; }
    public String setPassword(String password) { this.password = password ; }
 
    public String incAccess() { this.todayAccess++; }
    public String getAccessCount() { return this.todayAccess; }
 
}

다음부터는 프로그램에서 어떠한 객체가 전달되었을 때, 이 객체가 매핑 객체임을 알려면 isAnnotationPresent 메서드로 Entity 어노테이션이 선언되어 있는지 체크하면 간단하다.
그리고 프로그래머는 이 객체가 DB의 user_table과 관련이 있음을 알 수도 있다.
이제 더 생각을 해보자.
DB 매핑 작업중 이 객체의 필드(name, password, todayAccess)가 실제 테이블과 매핑이 되어 있는지 체크하는 로직이 필요해졌다고 해보자.
프로그래머는 당연히 todayAccess가 DB와는 관련없는 필드인 것을 알지만 프로그램은 알지 못하기에 일일히 DB 매핑 클래스마다 따로 체크하는 로직을 넣어야 한다.
하지만 그렇기엔 체크 로직이 매핑 클래스수만큼 늘어나게 되고 유연성은 떨어진다.
어노테이션을 사용하여 한번 고쳐보자.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
  String value();
}

DB 테이블의 실제 컬럼을 나타내는 필드 레벨의 어노테이션을 선언했다
그렇다면 클래스의 구성은 다음과 같이 바뀔 것이다.

@Entity("user_table")
public class User {
 
    @Column("username")
    String name;
 
    @Column("passwd")
    String password;
 
    int todayAccess:
 
    public String getName() { return this.name; }
    public String setName(String name) { this.name = name; }
 
    public String getPassword() { return this.password; }
    public String setPassword(String password) { this.password = password ; }
 
    public String incAccess() { this.todayAccess++; }
    public String getAccessCount() { return this.todayAccess; }
 
}

클래스가 한결 더 보기 쉬워졌다.
프로그래머가 봐도, JVM이 봐도, 이 클래스는 user 테이블의 쿼리 결과를 담는 객체이며, 해당 테이블의 컬럼값 두개를 name과 password에 담는 동작을 하는 것으로 알 수 있다.
여기서 좀 더 발전시킨다면 특정 액션 동작을 취할 시, 자동으로 이 클래스의 인스턴스를 생성하고 해당 DB에 쿼리한 뒤 결과를 담아 리턴해주는 자동 매핑 로직을 구현해볼 수도 있을 것이다.
실제로 Hibernate 가 비슷한 방식으로 DB와 객체의 매핑이 구현되어 있다.


Override 주석을 일관성 있게 사용하자

자바 1.5 배포판에 주석(annotation) 중에 일반 프로그래머에게 가장 중요한 것이 Override이다.

 
// 어디에 버그가 있을까?
import java.util.*;
 
public class Bigram {
    private final char first;
    private final char second;
    public Bigram(char first, char second) {
        this.first  = first;
        this.second = second;
    }
 
    public boolean equals(Bigram b) {
        return b.first == first && b.second == second;
    }
 
    public int hashCode() {
        return 31 * first + second;
    }
 
    public static void main(String[] args) {
        Set<Bigram> s = new HashSet<Bigram>();
        for (int i = 0; i < 10; i++)
            for (char ch = 'a'; ch <= 'z'; ch++)
                s.add(new Bigram(ch, ch));
        System.out.println(s.size());
    }
}
 
---------- execute ----------
260

Bigram 클래스 개발자는 equals 메소드를 (항목 8) 오버라이딩 하려고 했을 것이다. 그리고 hashCode를 (항목 9) 오버라이딩 하는 것도 잊지 않았다.
그러나 equals 메소드의 매개 변수를 Object 타입으로 정의하지 않았으므로, 오버라이딩 하는 대신 오버로딩 하게 되었다. (항목 41)
@Override 주석을 사용하면 컴파일 도중에 에러를 찾아낼 수 있다. 그 메세지는 아래와 같다.

The method equals(Bigram) of type Bigram must override or implement a supertype method Bigram.java
 @Override
    public boolean equals(Object o) {
        if(!(o instanceof Bigram))
            return false;
 
        Bigram b = (Bigram) o;
        return b.first == first && b.second == second;
    }
 
---------- execute ----------
26

수퍼 클래스의 메소드를 오버라이드 한다고 생각되는 모든 메소드에 Override 주석을 사용해야 한다.
단, 이 규칙에 한가지 예외가 있다. 실체 클래스(abstract가 붙지않은)를 작성하면서 추상 메소드를 오버라이트 한다면, 그 메소드에 Override 주석을 달아줄 필요가 없다.
제대로 오버라이드 하지 않으면, 컴파일러가 알아서 에러메세지를 발생시키기 때문이다.

요약 : 수퍼 타입의 메소드를 오버라이드 하는 모든 메소드에 Override 주석을 달아주면, 컴파일러가 굉장히 많은 에러를 막아줄 수 있다. 한 가지 예외가 있는데, 실체 클래스에서는 수퍼 클래스의 추상 메소드를 오버라이드하는 메소드에 주석을 달 필요없다.

creative by javacafe

#010 열거형(Enum)과 주석(Annotation)

자바 1.5 배포판에는 enum 타입이라는 새로운 클래스와 annotation이라는 새로운 인터페이스가 추가되었다.
이 장에서는 새로운 타입들을 잘 사용할 수 있는 방법을 설명한다.


int 상수 대신 enum을 사용하자
int 상수에 비해 enum 타입은 훨씬 더 가독성이 좋고, 안전하며, 강력하다.

// int enum 패턴 - 너무 빈약하다!
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
 
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;

int enum 패턴의 단점

– 타입 안전을 보장하는 방법이나 편리하게 사용할 수 있는 방법을 제공하지 않는다. (예. 오렌지를 인자로 받는 메소드에 사과를 전달하고 오렌지와 사과를 == 연산자로 비교해도 컴파일 에러가 나지 않는다)
– int enum 상수와 연관된 int 값이 변경되면 클라이언트 코드를 다시 컴파일 해야 한다.
– int enum 상수를 출력 가능한 문자열로 쉽게 바꾸는 방법도 없다.
public enum Apple {FUJI, PIPPIN, GRANNY_SMITH}
public enum Orange {NAVEL, TEMPLE, BLOOD}

enum 타입의 장점

– 컴파일 시점의 타입 안전을 제공한다.
– 각 enum 타입은 자신의 네임 스페이스를 갖기 때문에, 동일한 이름의 상수를 갖는 enum 타입의 공존이 가능하다.
– enum 타입의 toString 메소드를 호출하면 enum을 출력 가능한 문자열로 변환할 수 있다.
– 자기 나름의 메소드와 필드를 추가할 수 있고 인터페이스를 구현할 수 있다.
// 데이터와 메소드를 갖는 enum 타입
public enum Planet {
    MERCURY(3.302e+23, 2.439e6),
    VENUS  (4.869e+24, 6.052e6),
    EARTH  (5.975e+24, 6.378e6),
    MARS   (6.419e+23, 3.393e6),
    JUPITER(1.899e+27, 7.149e7),
    SATURN (5.685e+26, 6.027e7),
    URANUS (8.683e+25, 2.556e7),
    NEPTUNE(1.024e+26, 2.477e7);
    private final double mass;           // In kilograms
    private final double radius;         // In meters
    private final double surfaceGravity; // In m / s^2
 
    // 만유인력 상수 m^3 / kg s^2
    private static final double G = 6.67300E-11;
 
    // Constructor
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
        surfaceGravity = G * mass / (radius * radius);
    }
 
    public double mass()           { return mass; }
    public double radius()         { return radius; }
    public double surfaceGravity() { return surfaceGravity; }
 
    public double surfaceWeight(double mass) {
        return mass * surfaceGravity;  // F = ma
    }
}
 
// 특정 객체의 지구 무게를 명령행 인자(175)로 받아서 그 객체의 모든 8개 행성에서의 무게를 출력
public class WeightTable {
   public static void main(String[] args) {
      double earthWeight = Double.parseDouble(args[0]);
      double mass = earthWeight / Planet.EARTH.surfaceGravity();
      for (Planet p : Planet.values())
          System.out.printf("Weight on %s is %f%n",
                            p, p.surfaceWeight(mass));
   }
}
 
---------- execute ----------
Weight on MERCURY is 66.133672
Weight on VENUS is 158.383926
Weight on EARTH is 175.000000
Weight on MARS is 66.430699
Weight on JUPITER is 442.693902
Weight on SATURN is 186.464970
Weight on URANUS is 158.349709
Weight on NEPTUNE is 198.846116

– Object 클래스의 모든 메소드를 양질로 구현한 메소드를 제공하며, Comparable과 Serializable 인터페이스를 구현하고 있다.

creative by java

#009 제네릭 두번째

제네릭 메소드를 애용하자

// 원천 타입을 사용 - 바람직하지 않다! (항목 23)
public static Set union(Set s1, Set s2) {	// 경고 - Set is a raw type. References to generic type Set<E> should be parameterized
	Set result = new HashSet(s1);	// 경고 - HashSet is a raw type. References to generic type HashSet<E> should be parameterized
	result.addAll(s2);			// 경고 - Type safety: The method addAll(Collection) belongs to the raw type Set. References to generic type Set<E> should be parameterized
	return result;
}

위의 코드는 컴파일은 되지만 경고 메시지가 나온다. 이 경고 메세지를 없애고 메소드가 타입 안전하게 만들려면, 아래와 같이 코딩한다.

import java.util.*; 
 
public class Union {
 
    // 제네릭 메소드
    public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
        Set<E> result = new HashSet<E>(s1);
        result.addAll(s2);
        return result;
    }
 
    // 제네릭 메소드를 사용하는 간단한 프로그램
    public static void main(String[] args) {
        Set<String> guys = new HashSet<String>(
            Arrays.asList("Tom", "Dick", "Harry"));
        Set<String> stooges = new HashSet<String>(
            Arrays.asList("Larry", "Moe", "Curly"));
        Set<String> aflCio = union(guys, stooges);
        System.out.println(aflCio);
    }
}
 
---------- execute ----------
[Moe, Harry, Tom, Curly, Larry, Dick]

union 메소드는 세 Set(메소드 인자 2개와 반환값 1개) 모두의 타입이 같아야 한다는 제약을 갖는데, 이를 더 유연하게 만들고 싶으면 바운드 와일드 카드 타입(bounded wildcard types)을 사용하면 된다.


바운드 와일드 카드를 사용해서 API의 유연성을 높이자
메소드 API에 와일드 카드 타입을 사용하면, 코드 작성이 조금 어려워지긴 하지만 훨씬 유연한 API를 만들 수 있다.
만일 폭넓게 사용할 라이브러리를 작성한다면, 와일드 카드 타입을 올바르게 사용하도록 해야한다.

 import java.util.*;
 
public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
 
    // The elements array will contain only E instances from push(E).
    // This is sufficient to ensure type safety, but the runtime
    // type of the array won't be E[]; it will always be Object[]!
    @SuppressWarnings("unchecked") 
        public Stack() {
        elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
    }
 
    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }
 
    public E pop() {
        if (size==0)
            throw new EmptyStackException();
        E result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    }
 
    public boolean isEmpty() {
        return size == 0;
    }
 
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
 
     // Wildcard type for parameter that serves as an E producer
    public void pushAll(Iterable<? extends E> src) {
        for (E e : src)
            push(e);
    }
 
    // Wildcard type for parameter that serves as an E consumer
    public void popAll(Collection<? super E> dst) {
        while (!isEmpty())
            dst.add(pop());
    }
 
    // Little program to exercise our generic Stack
    public static void main(String[] args) {
        Stack<Number> numberStack = new Stack<Number>();
        Iterable<Integer> integers = Arrays.asList(3, 1, 4, 1, 5, 9);
        numberStack.pushAll(integers);
 
        Collection<Object> objects = new ArrayList<Object>();
        numberStack.popAll(objects);
 
        System.out.println(objects);
    }
}

pushAll 메소드에서 와일드 카드를 사용하지 않고 아래 코드와 같이 구현하게 되면,

   public void pushAll(Iterable<E> src) {
        for (E e : src)
            push(e);
    }

Line 68의 numberStack.pushAll(integers)에서 에러가 발생한다.

에러 메세지 – method pushAll(Iterable) in the type Stack is not applicable for the arguments (Iterable)

Stack는 Stack의 서브 타입이 아니기 때문이다.

pushAll의 인자 타입은 “E의 Iterable”이 아닌 “E의 어떤 서브타입의 Iterable”이 되어야 하므로, Iterable 바운드 와일드 카드 타입을 사용한다.
popAll 역시 와일드 카드를 사용하지 않고 아래 코드와 같이 구현하게 되면,

  public void popAll(Collection<E> dst) {
        while (!isEmpty())
            dst.add(pop());
    }

Line 71의 numberStack.popAll(objects)에서 에러가 발생한다.

에러 메세지 – The method popAll(Collection) in the type Stack is not applicable for the arguments

Collection Object는 Collection Number의 서브 타입이 아니기 때문이다.
popAll의 인자 타입은 “E 타입을 저장하는 Collection”이 아닌 “E의 어떤 슈퍼 타입을 저장하는 Collection”이 되어야 하므로, Collection 와일드 카드 타입을 사용하면 된다.


타입 안전이 보장되는 혼성(heterogeneous) 컨테이너의 사용을 고려하자
제네릭은 Set이나 Map과 같은 컬렉션과 ThreadLocal이나 AtomicReference 같은 단일 요소(single-element) 저장 컨테이너에 가장 많이 사용된다.
컨테이너의 특성에 따라 사용가능한 타입 매개변수의 개수가 자연스럽게 제한된다.

혼성이란? 컨테이너를 매개변수화 하지 않고 저장되는 요소의 키를 매개변수화 함으로써 서로 다른 타입의 객체들이 같은 컨테이너에 저장 및 검색될 수 있는 것을 혼성 컨테이너라고 부르고 있다.
컨테이너란? 객체를 저장하는 역할을 하는 클래스들을 포괄적으로 컨테이너라고 한다. 컬렉션 클래스들과 배열도 컨테이너이다.
// 타입 매개변수 E - 1개
public interface Set<E> extends Collection<E> {
    ... // 나머지 코드 생략
}
 
// 타입 매개변수 K, V - 2개
public interface Map<K,V> {
    ... // 나머지 코드 생략
}

때로는 더욱 유연한 컨테이너를 필요로 할 때가 있다.
예를 들어, 하나의 데이터베이스 행(row)는 많은 열(column)을 가질 수 있으며, 타입 안전이 보장되는 형태로 모든 열의 데이터 값을 사용할 수 있어서 좋다.
이런 효과를 낼 수 있는 간단한 방법이 컨테이너(container) 대신 키(key)를 매개변수화 하는 것이다.
그 방법에 대한 예제는 아래와 같다.

// 타입 안전이 보장되는 혼성 컨테이너
import java.util.*;
 
public class Favorites {
    //  타입 안전이 보장되는 혼성 컨테이너 패턴 - implementation
    private Map<Class<?>, Object> favorites =  // 주목할 점! - favorites Map의 값의 타입이 Object므로, 키와 값 간의 타입 관계가 유지되도록 보장하지 않는다
        new HashMap<Class<?>, Object>();
 
    public <T> void putFavorite(Class<T> type, T instance) {
        if (type == null)
            throw new NullPointerException("Type is null");
        favorites.put(type, instance);
    }
 
    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }
 
    //  타입 안전이 보장되는 혼성 컨테이너 패턴 - client
    public static void main(String[] args) {
        Favorites f = new Favorites();
        f.putFavorite(String.class, "Java");
        f.putFavorite(Integer.class, 0xcafebabe);
        f.putFavorite(Class.class, Favorites.class);
 
        String favoriteString = f.getFavorite(String.class);
        int favoriteInteger = f.getFavorite(Integer.class);
        Class<?> favoriteClass = f.getFavorite(Class.class);
        System.out.printf("%s %x %s%n", favoriteString,
                          favoriteInteger, favoriteClass.getName());
    }
}
 
---------- execute ----------
Java cafebabe Favorites

Favorites 인스턴스는 타입 안정이 보장된다(String을 요청하면 절대로 Integer를 반환하지 않는다).
Favorites 인스턴스는 혼성이다(일반적인 Map과 다르게 모든 키가 서로 다른 타입일 수 있다).
따라서, Favorites를 타입 안전 혼성 컨테이너(typesafe heterogeneous container)라고 한다.

creative by javacafe

#007 클래스와 인터페이스 세번째

자바에서는 클래스와 인터페이스를 설계하는데 사용할 수 있는 강력한 요소들을 제공한다. 이 장에서는 우리가 만드는 클래스와 인터페이스가 쓸모있고 강력하며 유연성이 있도록 하기 위해 도움을 주는 지침을 설명한다.


태그 (tagged) 클래스보다는 클래스 계층을 사용하자

태그(tagged) 클래스란 인스턴스들이 두 개 이상의 특성으로 분류되고 그런 특성을 나타내는 태그(tag) 필드를 갖는 클래스를 말한다.

// Tagged class - 클래스 계층보다 매우 조악하다!
class Figure {
    enum Shape { RECTANGLE, CIRCLE };
 
    // Tag field - 이 도형의 형태
    final Shape shape;
 
    // 이 필드는 shape가 RECTANGLE일 때만 사용된다.
    double length;
    double width;
 
    // 이 필드는 shape가 CIRCLE일 때만 사용된다.
    double radius;
 
    // circle 생성자
    Figure(double radius) {
        shape = Shape.CIRCLE;
        this.radius = radius;
    }
 
    // rectangle 생성자
    Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }
 
    double area() {
        switch(shape) {
          case RECTANGLE:
            return length * width;
          case CIRCLE:
            return Math.PI * (radius * radius);
          default:
            throw new AssertionError();
        }
    }
}

태그(tagged) 클래스는 단점 투성이이다.
열거형(enum) 선언, 태그 필드들, swith문 등을 포함해서 각종 진부한 코드들로 혼란스럽게 구성되어 있다.
다른 종류의 인스턴스에 속하는 부적절한 필드를 가지고 있어서 필요없는 메모리의 할당과 해지가 증가한다.
다른 종류의 인스턴스를 태그 클래스에 추가하려면 모든 switch문의 case를 추가해야 한다.
이런 문제를 해결하기 위해서 태그 클래스를 클래스 계층으로 변환한다.


태그 클래스를 클래스 계층으로 변환하는 방법

1. 태그 값에 따라 동작이 달라지는 각 메소드를 추상 메소드로 만든다.
– Figure 클래스의 area 메소드
2. 태그 값과 관계없이 동작하는 메소드가 있다면 추상 클래스에 넣는다.
– Figure 클래스에는 그런 메소드가 없다.
3. 모든 종류의 인스턴스들이 사용하는 필드들이 있다면 그것들도 추상 클래스에 넣는다.
– Figure 클래스에는그런 필드가 없다.
4. 태그 클래스의 각 인스턴스 종류를 루트 클래스의 서브클래스로 정의한다.
– Circle, Rectangle
5. 루트 클래스의 각 추상 메소드를 구현하는 메소드를 각 서브 클래스에 만든다.
– area 메소드
// 태그 클래스를 대체하는 클래스 계층 코드 - 루트 클래스
abstract class Figure {
    abstract double area();
}
// 루트 클래스의 서브 클래스(실체 클래스)
class Circle extends Figure {
    final double radius;
 
    Circle(double radius) { this.radius = radius; }
 
    double area() { return Math.PI * (radius * radius); }
}
// 루트 클래스의 서브 클래스(실체 클래스)
class Rectangle extends Figure {
    final double length;
    final double width;
 
    Rectangle(double length, double width) {
        this.length = length;
        this.width  = width;
    }
    double area() { return length * width; }
}

클래스 계층 코드는 앞에 나온 태그 클래스의 모든 단점을 해소시킨다.
또 다른 장점은, 타입들 간의 자연적인 계층관계를 반영할 수 있다는 것이다.
정사각형은 직사각형의 특별한 종류이므로(둘 다 불변이라고 가정하면), 클래스 계층에 포함될 정사각형 서브 클래스 코드는 다음과 같다.

class Square extends Rectangle {
    Square(double side) {
        super(side, side);
    }
}

앞에 나온 클래스 계층(Figure) 코드의 필드들은 접근자 메소드를 사용하지 않고 직접 접근된다는 점에 주목하자. 코드를 간결하게 만드느라 그런 것이므로, 클래스가 public이라면 그렇게 하면 안될 것이다(항목 14).

요약 : 태그 클래스는 적합하지 않으므로, 만일 태그 클래스를 갖는 클래스를 작성하고 싶다면, 태그를 없앨 수 있는지, 그리고 태그 클래스를 클래스 계층로 교체할 수 있는지에 대해 심사숙고하자.

전략을 표현할 때 함수 객체를 사용하자

What is a ‘Function-Object’?
자바는 객체지향을 구현하기 위해 모든 것을 클래스라는 일종의 인스턴스 템플릿으로 구현하도록 되어 있다.
단순한 유틸성 기능을 구현할 때에도 XXXUtils이라는 뭔가 우주에서 날아온 듯한 이름으로 클래스를 만들고 그 안에 static을 사용한 메서드로 기능을 구현한다.
자바는 언어 차원에서 모든 걸 클래스화 해야만 해서 여러 상황에서 불편한 점이 있을때가 많다.
간단한 예를 들어보자.
책에도 나와있듯이, 공식 API의 Camparator 는 정렬을 위한 함수 객체 인터페이스이다. 자바에서는 정렬 전략으로 사용하기 위해 저 클래스를 풀로 구현해야 한다.
이런 식으로 말이다.

// 이러한 문법적 껍데기를 반드시 선언해야 한다.
public class StringLengthComparator implements Comparator {
 
        // 우리가 필요한건 이 메소드의 기능 뿐인데도...
	public int compare(String s1, String s2) {
		return s1.length() - s2.length();
	}
}
// 아래와 같이 사용
Arrays.sort(array, new StringLengthComparator());

하지만 일부 언어에서는 함수 포인터(function pointer), 위임(delegate), 람다식(lamda expression) 또는 이와 유사한 기능을 제공하여 프로그램에서 특정 함수의 호출을 저장하거나 전달할 수 있다.

 lang="c">
// C의 함수 포인터 방식
#include 
void hello(char *name) {
    printf ("Hi %s\n", name);
}

int main() {
    // 반환값이 void 이고 매개변수가 캐릭터인 함수 포인터 선언
    void (*Func)(char *);

    // 그 포인터에 hello 가르키게 함
    Func = hello;

    // Func 는 변수로 사용할 수 있지만 본질은 함수이다.
    // 함수 포인터 실행
    Func("test");
}
// C#의 위임 방식
// delegate 타입 선언
public delegate void DoSomething(string command);
 
// 위임할 메서드
public void Action(string direction){
    // 메서드 구현
}
...
...
// delegate 생성
DoSomething done = new DoSomething(Action);
 
// 위임 객체를 통해 변수로서 함수를 취급가능하다.
done("left");
// 자바스크립트의 익명 람다 함수
// each의 람다 함수를 지정
$.each(function(key, value) {
    // each 구현
});

그렇다면, 위 기능을 새로 구현해보자. javascript 에서는 익명 함수를 지원하며, 위와 같은 구현이 아주 간단하다.

// 익명 함수를 사용한 배열 정렬법
arrayObject.sort(function(s1, s2) {
    return s1.length - s2.length;  
})

자바는 위 예제의 자바스크립트처럼 함수(혹은 메서드)만을 생성할 수 없고 반드시 클래스와 쌍으로 움직여야 한다.
그래서 새로운 업무 방식이 생겨 객체에 특정한 알고리즘을 교체하려면 아예 그 객체를 새로 만들거나(설마 이러진 않겠지요…), 그 업무 방식에 맞는 새로운 알고리즘을 구현한 메소드를 포함한 클래스를 생성하여 처리 메서드로 전달해야 한다.
이 굵은 글씨로 표현한 클래스 객체를 함수 객체 라고 부른다.
보통은 메서드를 하나만 가지는 인터페이스 형식으로 구현되나, 이 함수 객체를 사용하는 측에 따라서는 메서드 여러개가 구현되어 있을 수도 있다.


전략 패턴 (Strategy Pattern)

함수 객체가 제일 많이 쓰이는 곳은 역시 전략 패턴이다.
전략 패턴(Strategy Pattern) 은 기본적인 목적인 비슷하지만 흐름에서 변화하거나 교체할 수 있는 부분을 따로 분리하여 쉽게 변화시키고 새로 지정할 수 있게 해주는 패턴이다.
여러 메서드를 하나의 클래스에 집어넣은 것보다 유연성이 좋고 잘 디자인 된 전략은 다른 객체에서도 활용할 수 있다.
자바의 전략 패턴은 함수 객체를 사용해야 가능하다.

위에 예시로 든 Comparator 는 전략 패턴의 모범생 같은 구현이라고 볼 수 있겠다.
이제 예제와 함께 더 살펴보자.
만일 한 어느 학원 수강생 처리시스템에서 모든 등록 수강생의 주소를 새로운 주소로 바꾸는 작업을 해야 한다고 가정한다.
보통은 이렇게 처리할 것이다.

public List<Student> changeNewAddress(List<Student> student) {
    for(Student s : student) {
        String newAddr = getNewAddress(s.getAddress);
        s.setAddress(s);
    }
    return student;
}

수강생의 주소를 바꾸는 일 외에 학생들의 프로필 사진도 전부 섬네일화 해야 하는 작업이 생겼다고 가정해보자.
그렇다면 간단한 구현으로는 새로운 createProfilePhotoToThumb 메소드가 생길 것이다.

하지만 이렇게 접근하지 말고 다른 방법을 써 보자.
일단, 하려는 작업은 둘다 학생에 대한 배치 처리이다. 학생 하나하나를 순회하며 해당 학생의 특정 필드에 대해 작업을 수행한다.

여기서 “새로운 주소 변경” 과 “섬네일 만들기” 는 전략으로 볼 수 있고 위에서 설명한 함수 객체로 구현해볼 수 있다.
그렇다면 먼저 함수 객체 인터페이스는,

interface Transfer<T> {
    T transfer(T value);
}

그렇다면 전략 객체 구현은,

// 들어온 주소 문자열을 새 주소 변경하는 함수 객체 클래스.
class ChangeAdressJob implements Transfer<Student> {
    public Student transfer(Student address) {
        // 구 주소를 새 주소로 바꾸는 로직
    }
}
 
// 들어온 사진을 섬네일화 하는 함수 객체 클래스
class CreateThumbnail implements Transfer<Student> {
    public Student transfer(Student photo) {
        // 프로필 파일을 분석하여 해당 파일을 섬네일을 만듬
    }
}

그렇다면 이제 저 함수 객체를 사용하는 클래스는 다음과 같을 것이다.

class StudentManager {
 
    private final List<Transfer<Student>> transferStrategy = new LinkedList<Transfer<Student>>();
 
    public List<Student> loadStudents() { ... }
 
    // 배치 처리.
    // 학생 리스트에 대해 전략 함수 객체(Transfer 구현체)를 하나하나 실행함.
    public void executeBatch(List<Student> students) {
        for(Student s : students) {
            for(Transfer t : transferStrategy) {
                t.transfer(s);
            }            
        }
    }
 
    // 전략 객체를 전략 셋에 추가한다.
    public void addTransfer(Transfer t) {
        transferStrategy.add(t);
    }
 
    public static void main() {
 
        StudentManager manager = new StudentManager();
        List<Student> students = manager.loadStudents();
 
        manager.addTransfer(new ChangeAdressJob());
        manager.addTransfer(new CreateThumbnail());
 
        manager.executeBatch(students);
    }
}

static 멤버 클래스를 많이 사용하자

중첩(nested) 클래스는 다른 클래스의 내부에 정의된 클래스로, 외곽(enclosing) 클래스를 지원하는 목적으로만 존재해야 한다.
중첩 클래스에는 4가지 종류가 있는데, 이 항목에서는 이런 종류의 중첩 클래스를 언제, 왜 사용하는지 알려줄 것이다.

1. static 멤버 클래스

가장 간단한 중첩 클래스로써, 다른 클래스의 내부에 선언되어 있고 외곽 클래스의 모든 멤버들(private로 선언된 것까지도)을 사용할 수 있는 일반 클래스라고 생각하면 된다.
외곽 클래스의 static 멤버이므로, 다른 static 멤버와 동일한 접근 규칙을 준수해야 한다. (private로 선언되었다면 외곽 클래스의 내부에서만 사용 가능하다.)

2. static이 아닌 멤버 클래스

구문적으로 봐서, static 멤버 클래스와의 차이점은 static이 아닌 멤버 클래스는 static 수식어가 없다는 것이다.
구문적으로는 유사하지만 두 종류의 중첩 클래스는 서로 많이 다르다.
static이 아닌 멤버 클래스의 인스턴스는 자신과 연관되는 외곽 클래스의 인스턴스가 있어야만 생성할 수 있다.

3. 익명(anonymous) 클래스

익명 클래스는 명칭과 같이 이름이 없다. 자신을 포함하는 외곽의 클래스도 아니다.
다른 멤버와 함께 선언되지도 않고 사용 시점에서 선언과 인스턴스 생성이 동시에 이루어진다.
익명클래스는 함수 객체(function object) (항목 21)를 생성하는데 많이 사용된다.

4. 지역(local) 클래스

지역 클래스는 4가지 클래스 중 제일 적게 사용된다.
지역가 선언될 수 있는 곳이면 어디든 선언될 수 있으며, 지역변수와 동일한 유효 범위를 갖는다.
자바의 중첩 클래스 4가지
public final class IHaveMemberClasses {
 
    // 스태틱 멤버 클래스 선언
    public static class StaticMemberClass {}
 
    // 멤버 클래스 선언
    public class MeberClasss {}
 
    // 인터페이스를 받는 메서드
    public static void anonymouseClassMethod(Callable<String> call) {}
 
    public static void main() {
 
        // 지역 클래스 선언
        class LocalClass {};
 
        // 익명 클래스 사용.
        // 메소드 호출 시 바로 생성하여 전달함.
        IHaveMemberClasses.anonymouseClassMethod(new Callable<String>() {
            public String call() {
                return "call!";
            }
        });
 
        // 스태틱 멤버 클래스 생성. 그냥 사용가능
        // 만일 import 구문에 import IHaveMemberClasses.StaticMemberClass 를 써 놓으면
        // new StaticMemberClass() 로 그냥 사용할수도 있다.
        new IHaveMemberClasses.StaticMemberClass();
 
        // 멤버 클래스 생성.
        // 먼저 외곽 클래스가 생성되어야 사용할 수 있다.
        IHaveMemberClasses outer =  new IHaveMemberClasses();
        MeberClasss nonStaticMember = new outer.MeberClasss();
 
        // 로컬 클래스는 선언 메소드 스택 내에서 자유로이 사용 가능
        new LocalClasss();       
    }
}
요약 : 만일 멤버 클래스가 외곽 클래스의 인스턴스를 참조할 필요가 있다면 static이 아닌 멤버 클래스로 만들고, 그렇지 않다면 static 멤버 클래스로 만든다. 클래스가 어떤 메소드에 속한다는 가정하에, 만일 한 곳에서만 그 클래스의 인스턴스를 생성할 필요가 있고, 그 클래서의 특성을 나타내는 타입이 이미 존재한다면, 익명 클래스로 만들고 그렇지 않으면, 지역 클래스로 만든다.

creative by javacafe