안녕하세요. 개발자 Mommoo 입니다.

오늘은 CheckedExceptionUnCheckedException에 대해 포스팅 합니다.

해당 내용에 대해 알아보는것도 의미가 있습니다만, 포스팅 하는 핵심 부분은 CheckedException 의 단점에 대해 알아보는 것입니다. 해당 주제는 면접에서도 많이 나와요.


ChecekdException 과 UnCheckedException이란?

CheckedException 처리는 컴파일 시점에 명시적으로 예외상황을 처리해야하는 예외 기법입니다. 자바 CLASS 중 Exception 을 상속받아 만들 수 있습니다.

명시적으로 예외를 처리한다 함은, 자바 언어 기준으로, try~catch 구문을 사용하는것을 의미합니다. 명시적인 예외를 처리하지 않는다면 컴파일 되지 않아, 코드 에디터에서 빨간 줄이 그어져 있는것을 확인하 실 수 있습니다.

UnCheckedException 처리는 런타임 시점에 예외상황을 처리하는 예외 기법입니다. 자바 CLASS 중 RuntimeException 을 상속받아 만들 수 있습니다.

런타임 시점이란, 컴파일이 된 후 프로그램이 실행될 때, 어느 특정 상황을 의미합니다.

위에서 살펴본 CheckedException 예외와는 다르게, 명시적으로 처리할 필요가 없습니다. 개발자가 필요시에 try~catch 구문을 이용하여 처리합니다.


ChecekdException의 특징

CheckedException 은 사실 현대적인 언어에서는 제공하지 않는 경우가 많습니다. CheckedException 은 사실상 필요없다는 의견도 많을 뿐더러, 프로그래밍에 있어서 큰 단점을 가지고 있기 때문입니다. 하지만 장점도 가지고 있는데요, 해당 내용을 알아봅시다.

CheckedException 의 최고 장점은 아무래도 안정적인 소프트웨어를 만들 수 있는 길을 제시해준다는 점입니다. CheckedException 을 제공하는 개발자는 빈번하게 발생할 수 있는 예외 케이스 를 제공해줌으로써, 해당 메서드를 사용하는 개발자로 하여금 UnHappyPath 에 대해 생각할 수 있는 여지를 제공해줍니다.

물론, UnCheckedException도 예외케이스를 제공해주지만 아무래도 명시적으로 작성해야 하는 효과가 주는 안정성이 있다고 생각합니다.

하지만, 큰 단점이 있는데요. 그것은 항상 명시적으로 작성해야 한다는 점 입니다.

예를들어, FileInputStream("파일이름") 객체를 만든다고 가정해봅시다. 해당 객체는 FileNotFoundException 이라는 CheckedException 처리를 해야합니다. 해당 파일이름으로 된 파일이 없는 경우를 대비하라는 의미이지요. 그런데 해당 파일이 항상 존재한다고 가정할 수 있는 경우에는 어떨까요? 해당 경우에는 불필요한 try~catch 구문이 들어가, 코드 가독성을 해치게 됩니다. 또한 해당 파일이 항상 존재한다는 상황의 표현도 되지 않는것도 큰 문제라 볼수 있습니다.

프로그래밍이란 것은 아무래도 흐름을 통해 특정 상황을 만드는 일이 빈번하다 보니, CheckedException은 프로그래밍에 있어서 짜증나는 상황으로 다가오는 경우가 많습니다.


UnChecekdException의 특징

UnCheckedExceptionCheckedException 의 명확한 단점을 극복할 수 있는 장점이 있지만, 반대로 단점은 명시적으로 예외를 정의할수 있게 유도하지 못한다는 점입니다.

하지만 이는 예외상황은 어쨌거나, 프로그래머가 신경써야 한다는 점에 있어서 큰 단점이 되지 않습니다. 특정 메서드를 사용할 때는 메서드를 탐색하면 어떤 예외가 있는지 확인할 수 있기 때문에 예외 대처에 있어 큰 문제가 되지 않습니다.

그렇기에 현대적인 언어에서는 예외처리를 UnCheckedException 으로 제공하는 경우가 많습니다.


마무리

개인적인 생각으로는 숙련된 프로그래머는 CheckedException 의 단점이 더 부각되고, 초보 프로그래머에게는 CheckedException의 장점이 더 부각되는 상황이 될거 같습니다.

그렇기에 프로그래밍을 많이 할 수록 예외 처리는 RuntimeException 을 상속받아 처리하는 부분으로 빈번하게 처리되는거 같습니다.

글에는 개인적인 의견도 포함됬기 때문에 틀린점이 있을 수 있습니다. 틀린부분에 대해 의견주시면 감사히 받겠습니다.

글 읽어주셔서 감사합니다. :)

'Java' 카테고리의 다른 글

[Java] 파일 경로 처리하기.  (2) 2019.01.24
JAVA - JNI 사용하기  (2) 2017.09.02
JAVA - [SWING] LinearLayout 사용하기.  (0) 2017.08.25
JAVA - DownCasting(다운캐스팅)  (25) 2016.08.27
JAVA - HashMap key 구하기.  (1) 2016.08.25

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

오늘은 앞선 버블정렬 - 이론 포스팅에서 언급한 버븡절렬 개념을 토데로


코드를 만들려고 합니다. 


저번에 만들어 놓은 Sort 클래스를 상속 받아서 만들어 볼겁니다.


아래의 2개 포스팅을 참고하세요.


http://mommoo.tistory.com/65 - 버블정렬 이론 

http://mommoo.tistory.com/64 - Sort 클래스 설계



아래는 완성된 소스 입니다.




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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class BubbleSort extends Sort{
    private int version;
 
    public BubbleSort(int version){
        this.version = version;
        start();
    }
    
    /* Naming sort */
    @Override
    protected String getSortName() {
        if(version == 0 ) return "Normal Bubble Sort";
        else if(version == 1 ) return "Advance Bubble Sort";
        return "Bubble Sort";
    }
 
    @Override
    public void sort(){
        if(version == 0) normal();
        else if(version == 1) advance();
    }
 
    private void normal(){
        int size = DATA_LENGTH - 1;
        for(int i = 0 ; i < size ; size--){
            for(int index = 0 ; index < size; index++){
                /*
                   If the value of preceding array element is less than the value of target array element,
                   Swap two values
                */
                if(TARGET_DATA_ARRAY[index] > TARGET_DATA_ARRAY[index+1])
                    swap(index,index+1);
            }
        }
    }
 
    private void advance(){
        int size = DATA_LENGTH - 1;
        for(int i = 0 ; i < size ; size--){
            boolean isSwap = false;
            for(int index = 0 ; index < size; index++){
                /*
                   If the value of preceding array element is less than the value of target array element,
                   Swap two values
                   Any array element doesn't swap is that means already aligned.
                   So if isSwap variable is false, escape for-operator
                */
                if(TARGET_DATA_ARRAY[index] > TARGET_DATA_ARRAY[index+1]) {
                    isSwap = true;
                    swap(index,index+1);
                }
            }
            if(!isSwap) return;
        }
    }
}
 
cs




완성된 버블소트는 2가지 버전이 있습니다.


하나는 평범한 버블소트, 나머지는 개선된 버블소트 입니다.


Sort 클래스 덕분에, 지금 만드는 Bubble 소트는 sort 메서드에 로직을 기입하고,


생성자에서 start() 메서드만 사용해주면 끝입니다.


23번째의 normal() 메서드가 평범한 버블소트 로직입니다.


앞선 포스팅의 이론데로, 배열의 처음부터 끝까지 큰 숫자를 맨 뒤로 보낸후,


배열의 사이즈를 하나씩 줄여나가며, 그다음 큰숫자를 계속 뒤로 배치하는 방법입니다.


하지만 이 로직은, 배열의 사이즈가 0이 되기전에 이미 정렬이 완료되어 더이상 진행하지 않아도 되는 경우를 생각하지 않습니다.


하지만 개선된 버블소트는 만약 이미 정렬이 완료 되었다면 진행을 멈출수 있도록 로직이 구성되어있습니다.


37번째의 advance() 메서드가 개선된 버블소트 로직입니다.


이렇게 BubbleSort 클래스가 완성이 되었다면, 결과를 출력할 클래스를 하나 더 만들어야 합니다.


또한, 콘솔에 이쁘게 출력하기 위해 만든 클래스가 있습니다. 


해당 클래스는 따로 설명드리지 않고 그냥 잘 쓰셨으면 합니다.


아래에 2개의 클래스 코드가 있습니다.


SortRunner.java - 결과물 출력 클래스 (main 함수 위치)


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
32
33
34
35
36
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
 
/**
 * Created by mommoo on 2017-04-05.
 */
public class SortRunner {
    public static void main(String[] args){
 
        Sort[] sorts = new Sort[]{
                new BubbleSort(0),
                new BubbleSort(1)
        };
        
        /* Sort sort-objects */
        Arrays.sort(sorts);
 
        /* Create sort printing board */
        SortBoardMaker board = new SortBoardMaker(sorts);
 
        /* Write additional information */
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy. MM. dd. hh:mm");
        String author = "Author : Mommoo";
        String title = "Title : SortProject";
        String time = "Time : "+simpleDateFormat.format(Calendar.getInstance().getTime());
 
        /* Set additional information */
        board.setAdditionalInfo(new String[]{author,title,time,"Data Length : "+Sort.DATA_LENGTH});
 
        /* print board */
        System.out.println(board);
    }
}
cs




SortBoardMaker.java - 콘솔에 출력을 이쁘게 하기위해 만든 클래스


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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
 * Created by mommoo on 2017-04-18.
 */
public class SortBoardMaker {
    private final StringBuilder TOP_BOTTOM_BORDER_BUILDER = new StringBuilder();
    private final StringBuilder BLANK_LINE_BUILDER = new StringBuilder();
    private final StringBuilder CONTENT_BUILDER = new StringBuilder();
    private int textWidth;
    private String[] information;
 
    SortBoardMaker(Sort[] sorts){
 
        /* Make sort-object printing */
        for(int i = 0, size = sorts.length ; i < size ; i++){
            final int RANK = i+1;
            final String SORT_NAME = sorts[i].getRegulationSortName();
            final String SORT_TIME = sorts[i].getRegulationSpendTime();
            final boolean SORT_SUCCESS = sorts[i].isSortSuccess();
 
            CONTENT_BUILDER.append("##   ")
                    .append(RANK).append(".")
                    .append(" [ Name ] ")
                    .append(SORT_NAME)
                    .append(" [ Time ] ")
                    .append(SORT_TIME)
                    .append(" [ Sort Success ] ")
                    .append(SORT_SUCCESS)
                    .append("   ##")
                    .append("\n");
            /* Have to sub enter-character(\n) length */
            if(i == 0) textWidth = CONTENT_BUILDER.length() -1;
        }
 
        /* Make top and bottom line of board :  ################### */
        for (int i=0 ; i< textWidth ; i++ ) TOP_BOTTOM_BORDER_BUILDER.append("#");
 
        /* Make blank line of board : ##         ## */
        for (int i = 0 ; i < textWidth ; i++) BLANK_LINE_BUILDER.append(" ");
        BLANK_LINE_BUILDER.replace(0,1,"#");
        BLANK_LINE_BUILDER.replace(1,2,"#");
        BLANK_LINE_BUILDER.replace(textWidth - 2,textWidth - 1,"#");
        BLANK_LINE_BUILDER.replace(textWidth - 1,textWidth,"#");
 
    }
 
    public void setAdditionalInfo(String[] information){
        this.information = information;
    }
 
    @Override
    public String toString() {
 
        StringBuilder additionalInfo = new StringBuilder();
        StringBuilder tempBuilder = new StringBuilder();
        if (this.information != null){
            for(String info : this.information){
                tempBuilder.append(BLANK_LINE_BUILDER.toString());
                tempBuilder.replace(5,5+info.length(),info).append("\n");
                additionalInfo.append(tempBuilder.toString());
                tempBuilder.delete(0,additionalInfo.length());
            }
        }
 
        return tempBuilder
                .append(TOP_BOTTOM_BORDER_BUILDER).append("\n")
                .append(BLANK_LINE_BUILDER).append("\n")
                .append(additionalInfo)
                .append(BLANK_LINE_BUILDER).append("\n")
                .append(CONTENT_BUILDER)
                .append(BLANK_LINE_BUILDER).append("\n")
                .append(TOP_BOTTOM_BORDER_BUILDER)
                .toString();
    }
}
 
cs



SortRunner.java에서 main함수를 실행하면 아래와 같이 결과가 나옵니다.


본인이 원하는 정보를 수정 할 수 있게 클래스들을 작성하였으니, 알맞게 수정하시면 되겠습니다.




몇번 돌려보시면 확실히 개선된 버블소트가 항상 빠르게 나옵니다.


오늘 포스팅은 여기까지 입니다.


다음 코드 포스팅 역시, SortRunner 클래스와 Sort 클래스를 활용하여 선택정렬을 포스팅 할 예정입니다.


읽어주셔서 감사합니다.




포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee


안녕하세요. Mommoo 입니다.


오늘은 정렬 중에, 학습하기 쉬운 버블정렬에 대해 포스팅 합니다.


버블 정렬이 어떻게 진행되는지 gif파일로 만들어 봤습니다.


버블정렬?



물속에 있는 거품이 수면위로 보글보글 하게 올라오는 것처럼,


제일 무거운 원소가 배열의 최상단까지 올라가는 과정을 마치 거품이 올라가는 것과 같다 하여 


이름을 버블정렬이라 지었습니다.




버블정렬의 원리



만약 아래와 같은 배열이 있다고 가정해봅시다.


위의 배열을 내림차순으로 정렬 해보려고 합니다. (1 2 3 4 5 6)


버블정렬은 정렬이 안된 배열의 범위내에서, 서로 붙어 있는 배열 요소 2개씩 비교해 나갑니다.


붙어있는 배열요소 2개를 비교했을시, 큰쪽을 뒤쪽으로 배치 한후, 


처음 비교 요소중 2번째 요소와 다음 요소(3번째요소) 를 또 비교합니다. 마찬가지로 2개의 요소중 큰 요소를 배치하구요.


이런식으로 배열의 끝까지 비교를 했을시, 첫 배열범위에서 제일 큰 수가 맨 뒤로 배치 됩니다.


아래의 예시를 보시면 더욱 이해가 잘 될겁니다.




1세트를 마치게 되면 첫번째 배열 범위(1~6번째)에서 제일 큰 숫자인 6이 맨 뒤에 배치 됩니다.


또한, 1세트를 마치면 맨뒤에 요소 6은 정렬이 완료 된 상태 입니다. 따라서, 배열범위를 변경해야 합니다. (1~5번째로 변경)



아래는 2세트 에서의 예시 입니다.



2세트를 마치게 되면 바뀐 배열 범위(1~5번째)에서 제일 큰 숫자인 5가 배열 범위 맨 뒤에 배치됩니다.


또한, 맨뒤의 요소 5,6은 정렬이 완료 된 상태 이므로, 배열 범위를 변경합니다.(1~4번째로 변경)




이런식으로, 한세트가 끝나면, 배열 범위를 바꾼 후 2개씩 비교를 해 나가면, 배열 뒤쪽에서는 내림차순으로 정렬이 됩니다.


결과적으로, 버블정렬의 가장 큰 특징은 범위 내의 제일 큰 숫자를 뒤로 배치 하는 것과, 배열범위를 하나씩 줄여가는것 입니다.



지금까지의 설명을 읽고, 맨 위의 gif를 다시 본다면 재밌게 보실 수 있습니다!.



버블정렬의 성능


소트의 성능을 생각해볼때, 공간복잡도와 시간복잡도를 생각해보게 됩니다.


공간 복잡도는 말그데로, 배열만을 썻느냐, 아님 추가 배열을 썼느냐 등을 가리는 것이고


시간 복잡도는 정렬을 하는데 시간이 얼마나 소요 됬는지를 알아봅니다.


정렬은 공간 복잡도 보다, 시간 복잡도가 직결되므로 시간 복잡도만 알아볼것입니다.


시간복잡도는 보통 빅O 표기법으로 표시를 하는데,


빅O 표기법은 걸린시간의 최악의 경우만을 표기합니다. 


또한, 걸린 시간에서 부수적인 계산요소들은 생략하여 표기합니다.


버블정렬의 빅O 표기법을 구해보자면 아래와 같습니다.


만약 배열이 정렬 된 상태에서 정렬이 걸린시간을 생각하면, 한번만 비교해본후, 


정렬된걸 감지한후, 더이상 비교와 교환을 하지 않습니다. 


만약 배열의 크기가 n이라 하면, 교환은 한번도 이루어지지 않고, 비교만 (n번)합니다. (최선의 상황)


위의 배열 예시는 정렬이 하나도 되지 않은 최악의 상황입니다. 오히려 역으로 정렬된 상태입니다.


위의 배열은 비교를 n번 + n-1번 + n-2번 + n-3번 + n-4번 +.....+ 1번 하므로, 결국 n*(n+1)/2 의 비교횟수와


n*(n+1)/2의 교환 횟수를 가집니다. (최악의 상황


따라서 빅O 표기법의 조건중 하나인 최악의 상황 n*(n+1)/2으로 따지는데, 


이것을 아래와 같이 부수적인 계산요소들은 생략합니다.


따라서 빅O 표기법은 O(n^2) 입니다.


이는 앞으로 볼 정렬에 비교한다면, 느린편에 속합니다.


다음 포스팅은 버블정렬 - 자바코드 편을 포스팅 하겠습니다.


읽어주셔서 감사합니다.

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

안녕하세요. Mommoo 입니다.


앞으로 버블 정렬,선택 정렬, 삽입 정렬, 힙 정렬, 퀵 정렬을 만들어 볼텐데요,


이러한 정렬 들을 만들기 쉽게, 디버깅을 편하게 하기 위해 조상 클래스 Sort를 만들어 보고자 합니다.


기본목표는 아래와 같습니다.


    • 정렬들간의 비교분석을 위해 같은 배열 데이터를 참조하게 한다.
    • 데이터의 갯수만 변경하면 데이터가 갯수에 맞게, 랜덤값이 자동적으로 생성되게 한다.
    • 정렬들간의 비교를 위하여 공통된 행위 설계. ( sort할때 걸리는 시간, sort가 성공했는지 여부, 규격화된 디버깅 설계 등등...)
    • Comparable 인터페이스를 구현 해, Sort클래스를 상속받는 수 많은 정렬 클래스들이 시간 순으로 랭크화 하게 한다.


먼저 완성된 소스입니다.


특이점등을 설명해드리자면...


▶  6번의 DATA_LENGTH 변수는 추후에, 데이터 갯수를 표시하기 위해 public으로 공개했습니다.


▶  16번의 static 초기화 부분에서는 자동적으로 랜덤 값을 넣어주며, sort가 성공했는지 여부를 알기 위해

미리 sort된 배열도 하나 만들었습니다.


▶  각각의 자식 클래스마다 공통된 배열의 값을 참조를 해야하지만, 자식만의 배열을 가져야 하므로,

28번의 배열을 protected로 추가했습니다.


▶  36, 39번의 메서드는 각 자식 클래스마다 독립적인 내용이므로 자식에게 구현을 맡겼습니다.


▶  42번의 start 메서드는 자식 클래스가 start 메서드를 사용했을시, sort가 진행되고 sort 진행시간이 기록됩니다.

또한, 프린팅을 줄 맞춰서 예쁘게 하기위해, 프린팅 정보중 너비가 제일 긴 텍스트의 길이를 저장했습니다.


▶  58번의 swap 함수는 sort에서 빠지지 않는 swap기능을 미리 구현해놓았습니다.


▶  117번의 toString메서드는 자식 클래스의 소트가 진행됬을경우, 정보를 프린팅 하기위해 구현했습니다.


▶  129번의 compareTo 메서드는 Comparable을 구현하기위해 오버라이딩 했습니다. 추후에

자식클래스들을 시간순으로 정렬하기 위해 작성했습니다.


준비한 내용은 여기까지 입니다.


다음 포스팅은, 이러한 Sort 클래스를 상속받아 Bubble 소트를 만들어보겠습니다!

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

오늘은 Enum 자료형에 대해 포스팅 한다.

자바 1.5가 추가 됨으로써, 새로운 참조자료형이 추가 됬다.

그중 하나는, 열거자료형(Enum Type)이라 불리는

새로운 종류의 클래스이다.

보통 자바에서 상수의 열거는 아래와 같이 작성한다.


public static final int AMERICANO = 0;

public static final int CAFE_MOCHA = 1;

public static final int CAFE_LATTE = 2;


public static final int APPLE_JUICE = 0;

public static final int ORANGE_JUICE = 1;

public static final int GRAPE_JUICE = 2;


책에서 말하길, 이 기법은 int enum 기법이라 한다.

하지만, 이 기법은 치명적인 단점이 있다. AMERICANO 와 APPLE_JUICE는 우리가 보고있는 이름은 틀리지만,

값은 0으로 같은 상황이다. 즉, 아래와 같은 메서드가 있다고 가정할 시,


public static void makeCoffee(int coffeeType){

  //..........

}


해당 메서드는 APPLE_JUICE를 넣더라도 컴파일 오류 없이, 아메리카노를 넣은 것과 똑같이 잘 실행이 된다. 

이유는 위에서도 적었다 시피, 같은 0값이기 때문이다.

또한, 우리는 값 0으로 구분하는 것이 아닌 이름, 즉 변수명으로 구분한다. 변수명을 보고

이것이 아메리카노, 오렌지 쥬스등을 파악하기 때문이다. 하지만, int enum기법은 문자열로 변환하기가 녹록치 않다.

원하는 문자열로 변환해주는 메서드 if ~ else 조합으로 만들 수는 있겠지만, 해서는 안될 짓이다.

왜냐하면 아이템이 추가 될때마다, 단적으로 else를 추가해 줘야하고,  

그 메서드로 인해 영향을 받는 모든 코드들을 수정해야 하기때문에 유지보수에 큰 난항을 겪기 때문이다.


그렇다면, 문자열로 변환하기 쉽게 toString() 메서드를 제공하는, String 클래스를 이용하여 아래와 같이 열거형을 만들면 어떨까? 

public static final String AMERICANO = "0";

위와 같이 열거형을 구성하면, AMERICANO.toString()을 실행하면 원하는 문자열인 "AMERICANO"를 얻을 수 있지만,

String enum 패턴이라 불리는 이 패턴은 해서는 안 될 패턴이다. 

문자열 비교는 성능을 떨어 트릴 수 있고, 클라이언트 입장에서

인자를 넣기를 AMERICANO변수를 이용하지 않고, "0"으로 메서드를 실행 할 수 있는 가능성이 있기 때문에, 좋지 않다.

이렇게 됬었을 시, 설계자가 AMERICANO를 바꿨을시, 클라이언트의 코드는 원하느 결과가 나오지 않을 뿐더러,

컴파일 오류가 나지 않는 경우는 버그를 찾기 힘들기 때문이다.


이러한 문제점을 해결하기 위한 대안이 바로 열거형 자료형인 Enum 이다.

위에서 예로 적은 상수들을 아래와 같이 Enum 열거형으로 바꿔보았다.

public enum Coffee { AMERICANO, CAFE_MOCHA, CAFE_LATTE }

public enum JUICE { APPLE_JUICE, ORANGE_JUICE, GRAPE_JUICE }

위 enum클래스는 클라이언트가 접근 할 수 있는 생성자가 없다. 또한 final 이 내부적으로 선언되 있으므로,

객체생성, 상속이 되지 않는다. 


enum의 장점 중 하나는 컴파일 시점 시 형 안정성을 제공해준다. 

Coffee자료형은 반드시 Coffee안에 존재하는 열거 상수를 받아야 하지,

JUICE의 열거 상수가 들어가면 컴파일 오류가 발생한다. 

위에서 말한 int enum의 문제점인 makeCoffee안에 APPLE_JUICE를 넣더라도 실행되는걸 막을 수 있단 얘기이다.

또한 enum은 같은 값을 가진 상수들을 평화롭게 공존 시킬 수 있는데,

enum의 이름으로 각 상수들을 구분 할 수 있기 때문이다.

enum은 각 상수마다 toString 메서드를 사용 할 수 있어, 문자열로 쉽게 변환이 가능하다.

enum역시 클래스므로, 변수와 메서드를 추가 할 수 있다. 

위에서 작성한 열거 상수들은 변수가 아닌 상수임에 유의 하자.

변수와 메서드를 추가 하는 이유는 간단하다. 상수를 좀더 멋지게 사용하고 싶은 생각이 있는 것이다.


해당 내용은 다음 포스팅에서 계속한다.


포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

오늘 포스팅 할 내용은 Builder 패턴이다.

Class 를 설계하다 보면,

필수로 받아야 할 인자들이 있고,

선택적으로 받아야 할 인자들이 있는 경우가 있다.

필수적으로 받아야 할 인자를 받기 위해

생성자의 매개변수를 넣는다.

또한 선택적 인자를 받기 위해 추가적인

생성자를 만든다.


public class PersonInfo{

  private String name;         //필수적으로 받야할 정보

  private int age;                //선택적으로 받아도 되는 정보

  private int phonNumber;   //선택적으로 받아도 되는 정보

  public PersonInfo(String name){

     this.name = name;

  }

  public PersonInfo(String name, int age){

    this.name = name;

    this.age = age;

  }

  public PersonInfo(String name, int age, int phonNumber){

    this.name = name;

    this.age = age;

    this.phonNumber = phonNumber;

  }

}

이러한 상황을 Effective Java 2/E 에선 점층적 생성자 패턴이라 말한다.

하지만, 이러한 패턴은 잘 동작하지만, 몇가지 단점들이 존재한다.

첫번째로, 인자들이 많아 질 수록 생성자의 숫자 역시 많아진다.

두번째로, 매개변수의 이름으로 원하는 정보를 설명하지만,

같은 자료형은 실수로 바꿔 넣는 실수가 생길 수 있다.

예를 들어, 위의 예시에서 age와 phonNumber를 서로 바꿔 넣는 경우이다.

세번째로, 읽기 어려운 코드가 된다.

PersonInfo personInfo = new PersonInfo("Mommoo", 12, 119);

위의 코드는 매개변수의 정보를 설명할 수 없으므로,

personInfo 객체가 어떤 인자들이 들어갔는지 알기 위해선

PersonInfo 클래스의 매개변수 이름이나, 주석을 참고해야 한다.


이러한 단점들을 보완하기 위해, 자바 빈즈(beans) 패턴이 대안점으로 나온다.

해당 패턴은 웹프로그래밍을 할 때, 자주 쓰는 패턴이다.

public class PersonInfo{

  private String name;         //필수적으로 받야할 정보

  private int age;                //선택적으로 받아도 되는 정보

  private int phonNumber;   //선택적으로 받아도 되는 정보

  public PersonInfo(){

  }

  public void setName(String name){

    this.name = name;

  }

  public void setAge(int age){

    this.age = age;

  }

  public void setPhonNumber(int phonNumber){

    this.phonNumber = phonNumber;

  }

}

생성자의 단점으로 꼽혔던 가독성이 해당 패턴을 사용하는 경우, 어느정도 해결이 된다.

PersonInfo personInfo = new PersonInfo( );

personInfo.setName("Mommoo");         // 이름을 넣는 메서드

personInfo.setAge(12);                       // 나이를 넣는 메서드

personInfo.setPhonNumber(119);          // 전화번호를 넣는 메서드

자바빈즈 패턴은 코드량이 늘어나는 단점이 존재한다. 

하지만 그것보다 더욱 문제가 되는 것은 객체 일관성이 깨진다.

객체 일관성이 깨진다는 것이란, 한번 객체를 생성할때, 그 객체가 변할 여지가 존재한다는 뜻이다.

즉, set메서드는 언제 어디서든 사용 할 수 있다는 장점이 있지만, 객체의 불변성이 깨진다.

스레드 작업에 큰 단점이 될 수 있을 뿐더러, 컴파일러 오류는 아니지만, 우리가 원하지 않는

결과물이 만들어 질 수 있는것이다.


점층적 생성자패턴과 자바빈즈 패턴의 장점을 섞은것이 빌더패턴이다.

정보들은 자바 빈즈 패턴처럼 받되, 데이터 일관성을 위해 정보들을 다 받은후에 객체생성을 해준다.


public class PersonInfo{

  private String name;         //필수적으로 받야할 정보

  private int age;                //선택적으로 받아도 되는 정보

  private int phonNumber;   //선택적으로 받아도 되는 정보

  private PersonInfo(){

  }

  public static class Builder{

    private String name;

    private int age;

    private int phonNumber;

   

   public Builder(String name){

      this.name = name;

    }

   public Builder setAge(int age){

 this.age = age;

 return this;

   }

   public Builder setPhonNumber(int phonNumber){

 this.phonNumber = phonNumber;

 return this;

   }

   public PersonInfo build(){

   PersonInfo personInfo = new PersonInfo( );

   personInfo.name = name;

   personInfo.age = age;

   personInfo.phonNumber = phonNumber;

   return personInfo;

 }

}

이러한 빌더 패턴은 아래와 같이 사용한다.

PersonInfo personInfo = new Builder("Mommoo").setAge(12).setPhonNumber(119).build( );

setter에 리턴자료형을 Builder 객체를 지정함으로써, 메서드 체이닝 기법을 적용했고,

정보를 다 넣은 경우 build( ) 메서드로 객체생성을 한다.

build( ) 메서드를 쓴 이후 에는 PersonInfo 클래스의 멤버변수를 변경 할 수 있는 방법은

리플렉션 기법빼곤 존재하지 않는다. 따라서, 데이터 일관성, 객체불변성 등을 만족시킨다.

또한 코드 가독성 역시 올라간다.


하지만 이러한 빌드 패턴의 단점은 많은 코드량이 필요할 뿐더러,

Builder라는 객체를 하나더 만드는 것이므로, 때에 따라선 성능이 낮아질 수 있다.


Class 설계할때, 받아야할 인자들이 많거나, 선택적 인자들이 많거나 또한, 추가될 인자들이 많은 경우

에 Builder 패턴을 고려한다면, 좀 더 나은 설계를 할 수 있을 것이다.


포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

오늘 포스팅 할 내용은 정적 팩토리 메서드에 관한 내용이다.


내용 의 상당수는 (Effective Javaa 2/E - 2판)의 도움을 받았고,


개인적인 의견을 덧 붙여 해당 내용을 풀어 설명할 것 이다.


인스턴스를 생성하기 위해서는 Class 원칙에 따라, 생성자를 호출해야 한다.


Person p = new Person( );


Class의 이름은 캡슐화를 위해 Class의 내용을 아우를 수 있는 이름을 짓기 마련이다.


따라서, 생성자의 이름만을 봐서는 감이 안올때가 있다.


또한, 여러가지 매개변수를 받는 생성자를 만들 때도, 생성자의 매개변수를


설명할 방법이 딱히 없다. 하지만, 정적 팩토리 메서드의 활용 여부에 따라 


코드의 가독성을 높일 수 있다. (여기서 정적은 static을 가리킨다.)


그로인해 아래와 같은 장점들이 존재한다.


정적 팩토리 메서드는 해당 클래스 인스턴스에 이름을 지어줄 수 있다.


public class Person{

  private String name;

  private Person( ){

  }

  

  public static Person newInstance(){

    return new Person( );

  }

  public static Person newInstance(String name){

    this.name = name;

    return new Person( );

  }


  public static void main(String[] args){

    Person p  = Person.newInstance(); // 평범한 인스턴스 생성

    Person p2 = Person.newInstanceByName("Mommoo"); // name속성을 가지는 인스턴스 생성 

  }

}


인스턴스의 갯수를 제어 할 수 있다. 이를 응용한 것이 싱글톤 패턴 이다.


public class Person{

  private final Person p = new Person( );


  private Person( ){

  }


  public static Person getInstance(){

    return p;

  }


  public static void main(String[] args){

    Person p = Person.getInstance( ); // 이미 존재하고 있는 객체 리턴

  }

}


생성자엔 반환값이 없지만, 필요시에 적절한 반환값을 만들어 줄 수 있다.


public class Person{

  private Person( ){ 

  }


  public static class Doctor{

    private Doctor(){

    }

  }


  public static Doctor getDoctor( ){

    return new Doctor( );

  }


  public static void main(String[] args){

    Doctor doctor = Person.getDoctor( );

  }

}


위의 예시는 반환값을 특정 Class로 했지만, 설계에 따라 어떠한 반환값이든 가능하다.


생성자를 감싸는 구조이므로, 유지보수와 성능 개선에 좋다.


위의 예시중 getDoctor 메서드가 new Doctor( )란 새로운 인스턴스를 리턴하지만,


개발자가 싱글톤으로 리턴하는 코드로 변경한다고 가정해보자.


public class Client{


  public static void main(String[] args){

    /** 클라이언트는 변경사항이 없다. 

      *  getDoctor( ) 메서드가 내부적으로 어떻게 변경되는지, 어떻게 쓰이는지 알 필요조차 없다.

      *  클라이언트는 단순히 Doctor라는 객체를 쓰는데만 집중한다. 

      */

    Doctor doctor = Person.getDoctor( );  

  }

}


public class Person{

  private Person( ){ 

  }


  public static class Doctor{

    

    private final Doctor d = new Doctor( );


    private Doctor(){

    }

  }


  public static Doctor getDoctor( ){

    return Doctor.d;   //new Doctor( ) 에서 Doctor.d 로 변경되었다.

  }


  public static void main(String[] args){

    Doctor doctor = Person.getDoctor( );

  }

}


이러한 장점들이 있는 반면, 단점들은 무엇이 있을까? 


먼저, 정적 팩토리 메서드의 사용을 권하기 위해서 private 제어자를 사용해 생성자를 막는다.


그로인해, 해당 클래스를 상속 할 수 없다. 왜냐하면 상속받을 생성자가 없기 때문이다.


또한, 다른 정적 팩토리 메서드는 다른 메서드와 크게 구분이 가지 않는다. 


만약 생성자를 이용한다고 가정해보자.


클래스를 설계 할때는 클래스 이름과 동일하고 반환값 자리가 아예 존재 하지 않는


메서드라는 특정 표식이 있고, 클래스를 쓸때도, new 생성자( ); 처럼


new 연산자를 통해 생성하므로, 생성자의 느낌을 물씬 주지만


정적 팩토리 메서드는 다른 정적 메서들과 차이점이 존재하지 않는다.


Class를 설계한 개발자 조차도 주석을 달아 놓지 않으면, 후에 다시 자신이 설계한 클래스를


봤을 때, 잊어먹 을 수 있다. 


결국 정적 팩토리 메서드를 활용한 클래스를 사용할때는, API나 사용법을 들어야한다.


(물론 생성자를 사용할 때도 어느정도 API나 사용법등을 참고해야할 때가 있다.)




'Java [Effective Java 2_E]' 카테고리의 다른 글

JAVA - 열거자료형 Enum 사용하기(1)  (0) 2016.11.11
Java - Builder 패턴 적용하기.  (3) 2016.10.08

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

이번 포스팅은 저번에 이어


마지막 차례인 DownCating(다운 캐스팅)이다.


아래의 URL은 현재 기술할 내용을 이해하기 위해


필요한 내용을 기술한 포스팅이다.


http://mommoo.tistory.com/40 - 캐스팅

http://mommoo.tistory.com/41 - 업캐스팅


이전 업캐스팅 내용을 토데로 복습하자면


자바에서는 관련있는 데이터 끼리 형변환이 가능 했었다.


ex) (Child 클래스가 Parent 클래스를 상속받은 경우)


Parent parent = new Child();


윗 경우는 업캐스팅이라 했었고, 형변환 기호( (Parent) )를 붙여주지 않더라도


생략이 가능하다.


그렇다면 반대인 경우는 어떨까?


Child child = new Parent();


앞서 포스팅에서 기술 한 것처럼, 관련있는 데이터 끼리는


캐스팅이 가능하다고 했다. 하지만 , 위의 경우는 성립하지 않는다.


왜냐하면 앞서 포스팅에서 덧붙여 설명했던 개념인,


변수가 원하는 정보를 다 채워줘야하는 원칙에 어긋난다.


Child 클래스는 Parent 클래스를 상속받았기 때문에 Parent 클래스보다는 


Child 클래스가 더욱 많은 데이터를 가졌을 것이다.


즉,


child 변수가 원하는 정보는 Child 클래스의 데이터 전부를 원하는데,


Parent 인스턴스 ( new Parent(); ) 는 Parent 데이터만 가지고 있을 뿐,


Child의 데이터를 가지지 않는다. 그러므로 빨간줄이 그어지면서


컴파일 오류를 발생시킨다.



그렇다면, 위의 경우에서 형변환을 시켜준다면 어떨까?


Child child = (Child)new Parent();


개발툴에서 확인해보면, 컴파일 오류에서 벗어나면서 빨간줄이 사라질 것이다.


하지만, 저 코드는 런타임 오류가 발생한다.


이유는 아래와 같다.



"컴파일러에게 프로그래머가 형변환을 함으로써, 일단 데이터를 맞게 넣어준것 처럼 보여준다.


컴파일러는 문법이 맞다고 생각하여 넘어간다.


하지만, 프로그램이 실제로 동작할때, new Parent(); 인스턴스는 Child 형 데이터로 바꾸지 못한다는 것을


깨닫고, 런타임 오류를 뿜으며 프로그램이 종료된다. "



그렇다면, 왜 런타임 오류를 발생할까? 


그 이유는 JVM은 new Parent(); 인스턴스를 Child 데이터로 형변환 하려 했지만,


Child 데이터가 무엇인지 모르기 때문이다. 조금더 구체적인 이유는


Child 데이터는 만드는 프로그래머 마다 성질이 다를 것인데, 그것을 JVM 추리하지 못하기 때문이다.


이전 캐스팅 포스팅에서 나오길, 기본자료형 끼리는 추리가 가능하기 때문에,


알아서 알맞은 데이터 크기로 변환하여 넣어준다고 설명했다.


하지만, 위와 같이 참조형 데이터를 캐스팅 할때는, 


속성,성질이 정해져 있지 않은 참조형 데이터는 JVM이 알길 이 없다.


Child 데이터에 Parent 데이터를 넣는 경우는 화살표가 아래로 향하므로,


다운캐스팅이라 한다. 


위와 같은 예시에서 봤드시,


다운캐스팅은 일반적으로 성립하지 않는다. 


하지만, 다운캐스팅이 성립되는 경우가 존재한다.


아래의 예시를 보자.


Parent parent = new Child(); 


Child child = (Child)parent;


위의 예시는 다운 캐스팅이 성립되는 경우의 수이다.


왜 성립이 되는것일까?

 

parent 변수는 Parent클래스 형태의 변수지만, 


태생이 Child 인스턴스 인 데이터를 넣어주었다.


그러한 정보를 가지고 있는 parent 변수를 다시 Child 클래스형태로


다운캐스팅을 하였다.  parent변수 상태는 Parent 클래스형 상태이지만,


다운캐스팅을 해주는 경우 ( (Child)parent ) 태생이 Child 클래스 형이므로,


JVM이 parent 변수를 태생 정보인 Child 클래스 데이터 형으로 


다운캐스팅을 해줄 수 있는 것이다.




결과적으로, 다운캐스팅은 보통 성립하지 않는 문법이지만,


위와같이 업캐스팅이 선행된 경우, 다운캐스팅이 성립되는 경우가 존재한다.









'Java' 카테고리의 다른 글

JAVA - JNI 사용하기  (2) 2017.09.02
JAVA - [SWING] LinearLayout 사용하기.  (0) 2017.08.25
JAVA - HashMap key 구하기.  (1) 2016.08.25
JAVA - 변수 선언할때 m을 왜 붙일까?  (2) 2016.07.05
JAVA - UpCasting(업캐스팅)  (9) 2016.06.07

포스팅이 도움 되셨다면, 커피 한잔 후원해주세요!
더 좋은 포스팅 작성에 큰 힘이 됩니다.

Buy me a coffeeBuy me a coffee

+ Recent posts