https://github.com/SungWooSW/Travelog_springproject.git

 

GitHub - SungWooSW/Travelog_springproject: Travelog

Travelog. Contribute to SungWooSW/Travelog_springproject development by creating an account on GitHub.

github.com

 

나의 첫번째 springframework project! 다음 프로젝트도 진행하고 추후 자세한 설명을 해보자!

어떤 연결리스트를 순회하는 상황을 가정해보자.

 

 

사용자가 순회를 할 때, 다음 노드로 이동하기 위해서는 반드시 그 이전 노드에 대한 주소를 가지고 있어야 한다.

하지만, 어떤 노드의 주소를 직접 사용자에게 줄 수 없다면?

이때 등장하는 것이 Iterator!!

 

Iterator 클래스의 객체에는

필요한 그 주소를 private 멤버로 가지고 있으면서, 그 노드의 데이터를 반환하고 자신은 한칸 전진하는 등의 메서드를 포함하고 있다. 사용자는 각 노드의 주소를 알지는 못하지만, Iterator 객체를 이용해서 연결리스트를 순회할 수 있다. 

 

 

연결리스트에서의 Iterator에 대해 좀 더 자세히 살펴보자.

 

이를 적용하면, 아래와 같이 사용할 수 있다.

 

 

 

 

 

 

Theme. 리스트(list)

 

여러 개의 데이터를 저장하고, 임의의 위치에 새로운 데이터를 추가하거나, 데이터의 삭제가 가능하고, 임의의 위치의 데이터를 읽을 수 있고, 용량에 제한이 없고.... 모두 리스트의 여러 특징들 중에 하나이다.

 

이러한 리스트를 구현하는 대표적인 두 가지 방법은 배열(ArrayList), 연결리스트(LinkedList)가 있다.

리스트는 기본적으로 삽입(insert), 삭제(remove), 검색(search) 등 기본적인 연산을 제공하는데, 이러한 연산 과정에서 배열과 연결리스트의 장단점이 드러난다.

 

먼저, 배열의 가장 큰 특징 중 하나는 크기가 고정되어 있다는 것이다. 이로 인해, 데이터를 추가하려는데 배열의 크기를 넘어서게 된다면, 배열을 재할당(reallocation)해줘야 한다. 재할당하는 3가지 방법에 대해서는 아래 코드에 나타내도록 한다. 또한, 리스트의 중간에 원소를 삽입하거나 삭제할 경우 다수의 데이터를 옮겨야 한다. 가령, 원소를 삽입하려면 삽입하려는 위치(인덱스)부터 그 이후의 원소들을 모두 뒤로 한칸씩 밀어줘야하고, 삭제의 경우에는 앞으로 땡겨줘야 한다.

 

연결리스트는 배열과는 다르게 다른 데이터의 이동없이 중간에 데이터를 삽입하거나 삭제할 수 있다는 장점이 있고, 크기가 고정되어 있는 것이 아니므로 길이의 제한이 없다.

하지만, 랜덤 엑세스가 불가능하다는 단점이 있다. 

랜덤 엑세스에 대해 간단하게 설명하면,

먼저 연결리스트는 아래와 같이 노드들의 연결된 형태이다.(head는 첫번째 노드의 주소를 저장하고 있다.)

이때, 각각의 노드들이 다음 노드들의 주소를 링크 필드에 저장하고 있기 때문에 어떤 노드에 접근하고 싶다면, 첫번째 노드부터 순차적으로 주소를 따라가면서 접근해야 한다. 이는 배열에서 특정 원소에 접근하고 싶다면 해당 인덱스로 바로 접근할 수 있는 것과 차이가 있다. 즉, 배열은 랜덤 엑세스가 가능하지만, 연결리스트는 랜덤 엑세스가 불가능하다.

 

 

Theme. ArrayList의 구현

MyArrayList 클래스를 생성하여 ArrayList의 일부 메서드들을 구현하도록 한다.

 

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import java.util.Arrays;
 
// 하나의 array 리스트를 표현하는 클래스
public class MyArrayList<E> {
 
    private static final int INIT_CAPACITY = 10;
    private E[] theData;
    private int size; // 현재 배열의 크기
    private int capacity;
 
    public MyArrayList() {
        theData = (E[]) new Object[INIT_CAPACITY];
        size = 0;
        capacity = INIT_CAPACITY;
    }
 
    // 해당 인덱스에 데이터를 추가
    public void add(int index, E anEntry) {
 
        if (index < 0 || index > size) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        if (size >= capacity) {
            reallocate();
        }
        for (int i = size - 1; i >= index; i--) { // 해당 인덱스를 포함하여 그 이후의 모든 데이터들을 한칸씩 뒤로 보내줌
            theData[i + 1= theData[i];
        }
        theData[index] = anEntry;
        size++;
 
    }
 
    // 배열재할당! 배열의 값은 그대로 두고, 배열의 크기를 증가시킴
    private void reallocate() {
        // 방법 1
        E[] tmp = (E[]) new Object[capacity * 2];
        for (int i = 0; i < size; i++) {
            tmp[i] = theData[i];
        }
        theData = tmp;
        capacity *= 2;
 
        // 방법 2
        capacity *= 2;
        E[] tmp = (E[]) new Object[capacity];
        System.arraycopy(theData, 0, tmp, 0, size);
        theData = tmp;
 
        // 방법 3
        capacity *= 2;
        theData = Arrays.copyOf(theData, capacity);
    }
 
    // 데이터를 추가(맨 뒤에)
    public void add(E anEntry) {
        add(size, anEntry);
    }
 
    // 배열 내 동일한 데이터가 존재하는지를 판단하고, 있다면 그 위치(인덱스)를 반환
    public int indexOf(E anEntry) {
        for (int i = 0; i < size; i++) {
            if (theData[i].equals(anEntry)) {
                return i;
            }
        }
        return -1;
    }
 
    // get, set
    public E get(int index) {
        if (index < 0 || index >= size) {
            throw new ArrayIndexOutOfBoundsException(index);
        } else {
            return theData[index];
        }
    }
 
    public E set(int index, E newValue) {
        if (index < 0 || index >= size) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        E oldValue = theData[index];
        theData[index] = newValue;
        return oldValue;
    }
 
    // remove
    public E remove(int index) {
        if (index < 0 || index >= size) {
            throw new ArrayIndexOutOfBoundsException(index);
        } 
        E returnValue = theData[index];
        for (int i = index + 1; i < size; i++) {
            theData[i - 1= theData[i];
        }
        size--;
        return returnValue;
    }
 
}
 
cs

 

 

Theme. SingleLinkedList의 구현

1. 연결 리스트에서 하나의 노드를 표현하기 위한 클래스

 

1
2
3
4
5
6
7
8
9
10
11
12
13
// 연결리스트에서 하나의 노드를 표현하기 위한 클래스
public class Node<T> {
 
    public T data; // 노드의 데이터 필드
    public Node<T> next; // 노드의 링크필드
 
    public Node(T item) {
        data = item;
        next = null;
    }
 
}
 
cs

 

2. 하나의 연결리스트를 표현하는 클래스

 

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// 하나의 연결리스트를 표현하는 클래스
public class MySingleLinkedList<T> {
    public Node<T> head; // 첫번째 노드의 주소 참조
    public int size; // 노드의 개수
 
    public MySingleLinkedList() {
        head = null;
        size = 0;
    }
 
    // 연결리스트 첫번째 노드 앞에 새로운 노드를 추가하는 메서드
    public void addFirst(T item) {
        Node<T> temp = new Node<T>(item); // 새로운 노드를 생성하고 추가할 데이터 저장
        temp.next = head; // 새로운 노드의 next(링크필드)가 기존 첫번째 노드 주소를 저장
        head = temp; // head가 새로운 첫번째 노드 주소를 저장
        size++// 노드가 추가됐으니 노드의 개수 += 1
        
        /* 주의할 점 정리 */
        /*
        Node<T> newNode = new Node<T>(item); // T를 type parameter로 가지는 다른 클래스의 객체 생성 가능
        Node<T>[] arr1 = new Node<T>[100]; // T를 type parameter로 가지는 다른 클래스의 배열 생성 불가능
        
        T t = new T(); // T 타입의 객체 생성 불가능
        T[] arr2 = new T[100]; // T 타입의 배열 생성 불가능
        */
        
    }
 
    // 이전 노드 객체가 매개변수로 주어졌을 때, 그 다음에 새로운 노드를 추가하는 메서드
    public void addAfter(Node<T> before, T item) {
        Node<T> temp = new Node<T>(item); // 새로운 노드를 생성하고 추가할 데이터 저장
        temp.next = before.next; // 새로운 노드의 next(링크필드)가 before 노드가 저장하던 다음 노드의 주소를 저장
        before.next = temp; // before 노드의 next가 새로운 노드의 주소를 저장
        size++// 노드가 추가됐으니 노드의 개수 += 1
    }
 
    // 연결리스트의 첫번째 노드(head 노드)를 삭제하고 그 노드에 저장된 데이터를 반환하는 메서드
    public T removeFirst() {
        if (head == null) {
            return null;
        } else {
            T temp = head.data; // 삭제될 첫번째 노드의 데이터를 temp에 저장
            head = head.next; // head가 현재의 head 노드의 다음 노드를 가리키게 됨
            size--// 노드가 삭제됐으니 노드의 개수 -= 1
            return temp; // 저장된 데이터 반환
        }
    }
 
    // before가 가리키는 노드의 다음 노드를 삭제하고 그 노드에 저장된 데이터를 반환하는 메서드
    public T removeAfter(Node<T> before) {
        if (before.next == null) { // 삭제하려는 노드가 null인 경우
            return null
        } else {
            T temp = before.next.data;
            before.next = before.next.next; // 삭제될 노드의 다음 노드의 주소를 before가 가리키는 노드의 next가 저장
            size--// 노드가 삭제됐으니 노드의 개수 -= 1
            return temp; // 저장된 데이터 반환
        }
    }
 
    // 매개변수 item과 동일한 data를 가지는 노드의 인덱스 넘버를 반환하는 메서드(찾지 못하면 -1 반환)
    public int indexOf(T item) {
        // 연결리스트의 노드들을 처음부터 순서대로 방문하는 순회(traverse) 이용!
        Node<T> p = head; // 순회를 하면서 각 노드들을 가리킬 변수 p 선언하고, head가 가리키는 주소 저장
        int index = 0;
        while (p != null) { // 끝까지 돌도록
            if (p.data.equals(item)) { // equals 메서드를 사용하는 것!
                return index;
            }
            p = p.next; // p가 다음 노드를 가리키도록! ( 한칸 전진 )
            index++// index도 다음 노드로 넘어가니까 +1
        }
        return -1;
    }
 
    // 연결리스트의 index번째 노드의 주소를 반환
    public Node<T> getNode(int index) {
        if (index < 0 || index >= size) { // index가 연결리스트의 범위를 벗어난 경우
            return null;
        } else {
            Node<T> p = head; // 순회를 하면서 각 노드들을 가리킬 변수 p 선언하고, head가 가리키는 주소 저장
            for (int i = 0; i < index; i++) { // 주어진 index번째 노드를 가리키도록 p를 전진시킨다.
                p = p.next;
            }
            return p;
        }
    }
 
    // 연결리스트의 index번째 저장된 데이터를 반환
    public T get(int index) {
        if (index < 0 || index >= size) { // index가 연결리스트의 범위를 벗어난 경우
            return null;
        } else {
            Node<T> p = head; // 순회를 하면서 각 노드들을 가리킬 변수 p 선언하고, head가 가리키는 주소 저장
            for (int i = 0; i < index; i++) { // 주어진 index번째 노드를 가리키도록 p를 전진시킨다.
                p = p.next;
            }
            return p.data;
        }
    }
 
    // 연결리스트의 index번째 위치에 새로운 노드를 삽입
    public void add(int index, T item) {
 
        if (index < 0 || index > size) { // index가 연결리스트의 범위를 벗어난 경우
            return
        }
        if (index == 0) {
            addFirst(item);
//            Node<T> temp = new Node<T>(item);
//            temp.next = head;
//            head = temp;
//            size++;
        } else {
            Node<T> node = getNode(index - 1);
            addAfter(node, item);
//            Node<T> p = head;
//            for (int i = 0; i < index - 1; i++) {
//                p = p.next;    // p는 index-1번째 노드를 가리키고 있음
//            }
//            Node<T> temp = new Node<T>(item);
//            temp.next = p.next;    // index번째 노드의 주소를 temp의 next에 저장
//            p.next = temp;    // 새로 추가된 노드를 index-1번째 노드의 next가 가리키도록 함
//            size++;
        }
    }
 
    // 연결리스트의 index번째 노드를 삭제하고, 그 노드에 저장된 데이터를 반환
    public T remove(int index) {
        if (index < 0 || index >= size) { // index가 연결리스트의 범위를 벗어난 경우
            return null;
        }
 
        if (index == 0) {
            return removeFirst();
//            Node<T> temp = head;
//            head = head.next;
//            size--;
//            return temp.data;
        } else {
            Node<T> prev = getNode(index - 1);
            return removeAfter(prev);
//            Node<T> p = head;
//            for (int i = 0; i < index-1; i++) {
//                p = p.next;    // index-1 번째 노드 가리키고 있음
//            }
//            Node<T> temp = p.next; // index번째 노드 가리킴
//            p.next = temp.next;
//            size--;
//            return temp.data;
        }
 
    }
 
    // 매개변수로 주어진 값과 동일한 데이터값을 가지는 노드를 찾아 삭제하고, 삭제된 노드에 저장된 데이터 값을 반환
    public T remove(T item) {
        // q는 항상 p의 직전노드를 가리키게 하고 순회!
        Node<T> p = head;
        Node<T> q = null;
        while (p != null && !p.data.equals(item)) {
            q = p;
            p = p.next;
        }
        // while문을 빠져나왔을 때, p가 삭제할 노드를 가리키고 있다면, q는 바로 그 전 노드를 가리키고 있음
        if (p == null) {
            return null;
        }
        if (q == null) { // 첫번째 노드가 찾던 노드였던 경우(p는 첫번째 노드를 가리키고 있는 상태)
            return removeFirst();
        } else {
            return removeAfter(q);
        }
 
    }
 
    public static void main(String[] args) {
        
    }
 
}
 
cs

 

 

 

Theme. 추상 클래스

 선언만 있고 구현이 없는 메서드를 추상(abstract) 메서드라고 한다. 이러한 추상 메서드를 하나라도 포함한 클래스는 추상 클래스이다.

 추상 메서드와 추상 클래스는 키워드 abstract로 표시한다.

 하지만, 추상 클래스는 객체를 만들 수 없다. 따라서 서브 클래스를 만드는 용도로만 사용이 된다. (이때 추상 클래스가 수퍼 클래스가 되는 것)

 

블로그 내 "Scheduler 프로그램(java)" 게시물에서 추상 클래스를 사용하고 있는데,

superclass인 Event class를 다음과 같이 추상 클래스로 만들었고, 추상 메서드를 나타냈다.

 

 

이후, subclass인 OneDayEvent, DurationEvent, DeadlinedEvent class마다

isRelevant(MyDate date) 메서드를 구현하여 사용하였다. 상속관계이므로!

각 클래스마다 return 값이 다른데,

메인 클래스에서 events 배열의 각 칸이 참조하고 있는 subclass들의 객체에 따라 isRelevant(MyDate date) 메서드가 실행되도록 작성해주었다. 

 

Theme. 인터페이스

인터페이스는 "극단적인 추상 클래스"라고 말할 수 있다. 

추상 클래스와 이를 상속받는(extends) 클래스는 추상 클래스에서 보통 메서드와 데이터 멤버들은 상속 받고, 추상 메서드의 경우에는 자신(subclass)의 클래스 내에서 구현하는 것이지만, 

인터페이스는 오로지 추상 메서드만 가지고 있기 때문에 이를 implements 하는 클래스는 오로지 그 추상 메서드들을 실제로 구현해야 한다.

 

Theme. Interface vs Abstract class

추상 메서드로만 구성된 추상 클래스는 인터페이스와 완전히 동일한가?

그렇지 않다!

Java에서는 "다중 상속"을 허용하지 않는다. 하지만,  하나의 클래스가 여러 개의 Interface를 implement하는 것은 가능하다.

 

Theme. 상속이란?

 

상속의 개념을 간단하게 말하자면,

어떤 클래스가 다른 클래스의 모든 멤버들을 포함하고, 그 외에 추가적으로 다른 멤버들을 가지는 것을 말한다.

이때, 데이터 멤버와 메서드를 모두 포함한다.

 

예를 통해 상속에 대해 이해해보자.

Computer class와 Notebook class가 있다.

 

Computer라는 클래스는 다음과 같은 멤버들을 가진다고 하자.

 

 

Notebook이라는 클래스의 경우에는 컴퓨터가 가지는 데이터 멤버나 메서드에 추가적으로,

screen size, weight의 데이터 멤버를 가진다. 

 

즉, 아래와 같은 관계를 가진다.

 

 

Notebook class는 Computer class가 가지고 있는 멤버들에 더하여 추가적으로 다른 멤버들을 가지고 있다.

이는 Computer class를 "확장"했다고 볼 수 있을 것이다.

따라서, Notebook class가 Computer class를 상속받았다는 것을 표현할 때, 

"public class Notebook extends Computer" ... 와 같이 "extends"를 적어준다.

 

만약, Notebook class의 객체를 아래와 같이 생성한다면,

Notebook nb = new Notebook();

그 객체 안에는 Computer class에 존재하는 멤버들이 함께 들어있다.

따라서,

nb.manufacturer = "LG";

nb.computePower();

와 같이 사용할 수 있다.

 

여기서 상속을 의미하는 용어들에 대해 정리할 필요가 있다.

먼저, "is-a" 관계란 말 그대로 "~는 ~의 일종이다."와 같이 이해하면 된다.

예시에서 A Notebook is a Computer. 라고 표현하면 노트북은 컴퓨터의 일종이다, 컴퓨터들 중 하나이다. 

따라서, 노트북은 컴퓨터를 상속받는다. 노트북과 컴퓨터는 "is-a" 관계이다.

 

또한, 컴퓨터가 superclass이면, 노트북은 subclass

컴퓨터가 baseclass이면, 노트북은 extended class

컴퓨터가 parent class이면, 노트북은 child class이다.

 

Theme. 상속과 생성자

상속에서 생성자와 관련하여 반드시 지켜야 하는 규칙이 있다. 

 

어떤 클래스가 있을 때, 작성된 코드에 생성자가 없을 경우 자동으로 no-parameter 생성자가 만들어진다. 하지만, parameter를 받는 생성자가 존재하는 등 생성자가 하나라도 있을 경우 생성자가 자동으로 만들어지지 않는다.

즉, Computer class에

public Computer(String man, String proc, int ram, int disk, double procSpeed) {
manufacturer = man;
processor = proc;
ramSize = ram;
diskSize = disk;
processorSpeed = procSpeed;
}

와 같이 생성자가 존재하는 경우, 자동으로 public Computer() { } 와 같은 no-parameter 생성자는 만들어지지 않는다.

 

모든 서브 클래스의 생성자는 먼저 수퍼 클래스의 생성자를 호출한다.

이때, super(..) 를 통해 명시적으로 호출해 주거나, 

그렇지 않을 경우에는 자동으로 no-parameter 생성자가 호출된다. 이 규칙에서 흔히 오류가 발생할 수 있는데,

이는 아래 코드를 통해 살펴보도록 하자.

 

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
package section1;
 
public class Computer {
 
    public String manufacturer; 
    public String processor;
    public int ramSize;
    public int diskSize;
    public double processorSpeed;
    
    public Computer(String man, String proc, int ram, int disk, double procSpeed) {
        manufacturer = man;
        processor = proc;
        ramSize = ram;
        diskSize = disk;
        processorSpeed = procSpeed;
    }
    
    public double computePower() {
        return ramSize * processorSpeed;
    }
    
    public double getRamSize() {
        return ramSize;
    }
    
    public double getProcessorSpeed() {
        return processorSpeed;
    }
    
    public int getDiskSize() {
        return diskSize;
    }
    
    public String toString() {
        String result = "Manufacturer: " + manufacturer +
        "\nCPU: " + processor +
        "\nRAM: " + ramSize + " megabytes" +
        "\nDisk: " + diskSize + " gigabytes" +
        "\nProcessor speed: " + processorSpeed +
        " gigahertz";
        return result;
        }
    
    
    
    
    
}
 
cs

 

이와 같이 Computer class에는 매개변수를 받는 생성자가 존재한다. 따라서 no-parameter 생성자는 만들어지지 않을 것이다. 

이때 아래와 같이 Notebook class를 작성하면 에러가 발생한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
package section1;
 
public class Notebook extends Computer {
    private double screenSize;
    private double weight;
 
    public Notebook(String man, String proc, int ram, int disk, double procSpeed, double screen, double weight) {
    
        screenSize = screen;
        this.weight = weight;
    }
}
 
cs

 

왜 에러가 발생할까?

 

현재 서브 클래스인 Notebook 클래스의 생성자에서 수퍼 클래스인 Computer 클래스의 생성자를 호출하고 있지 않다.

따라서, 자동으로 no-parameter 생성자가 호출될 것이고, "super()" 라고 호출되면서 Computer 클래스의 no-parameter 생성자를 호출하고자 한다. 

하지만! 수퍼 클래스인 Computer 클래스에는 no-parameter 생성자가 존재하지도 않고, 자동으로 만들어지지도 않는 상태이다. 왜냐하면 이미 parameter를 받는 생성자가 존재하기 때문이다.

있지도 않은 생성자를 호출하려고 하니 에러가 발생하는 것이다.

 

만약 Computer 클래스에 아무런 생성자가 작성되어 있지 않은 상황이었다면 에러가 발생하지 않았을 것이다.

왜냐하면 Computer 클래스에서 자동으로 no-parameter 생성자가 호출되고, Notebook 클래스에서는 자동으로 수퍼 클래스의 no-parameter 생성자를 호출할 것이기 때문이다.

 

에러를 해결하기 위해서는 다음과 같이 작성해야 한다.

Computer 클래스의 생성자가 매개변수로 

String man, String proc, int ram, int disk, double procSpeed

를 받고 있으니, Notebook 클래스의 생성자에서도 super(......) 안에 수퍼 클래스에 맞춰 매개변수를 적어줬다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
package section1;
 
public class Notebook extends Computer {
    private double screenSize;
    private double weight;
 
    public Notebook(String man, String proc, int ram, int disk, double procSpeed, double screen, double weight) {
        super(man, proc, ram, disk, procSpeed); // 에러 해결!
        screenSize = screen;
        this.weight = weight;
    }
}
 
cs

 

Theme. Method Overriding(오버라이딩)

 

자식 클래스는 부모 클래스의 데이터 멤버나 메서드를 모두 상속받게 된다. 이때, 부모로부터 상속받은 메서드를 자식클래스에서 보다 목적에 맞게 사용하고 싶다면, 그 메서드 일부를 수정(덮어쓴다)할 수 있다. 이것이 오버라이딩이다. 이때, 메서드의 이름은 똑같아야 한다.

 

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
package section1;
 
public class Computer {
 
    public String manufacturer; 
    public String processor;
    public int ramSize;
    public int diskSize;
    public double processorSpeed;
    
    public Computer(String man, String proc, int ram, int disk, double procSpeed) {
        manufacturer = man;
        processor = proc;
        ramSize = ram;
        diskSize = disk;
        processorSpeed = procSpeed;
    }
    
    public double computePower() {
        return ramSize * processorSpeed;
    }
    
    public double getRamSize() {
        return ramSize;
    }
    
    public double getProcessorSpeed() {
        return processorSpeed;
    }
    
    public int getDiskSize() {
        return diskSize;
    }
    
    public String toString() {
        String result = "Manufacturer: " + manufacturer +
        "\nCPU: " + processor +
        "\nRAM: " + ramSize + " megabytes" +
        "\nDisk: " + diskSize + " gigabytes" +
        "\nProcessor speed: " + processorSpeed +
        " gigahertz";
        return result;
        }
}
 
cs

 

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
package section1;
 
public class Notebook extends Computer {
    private double screenSize;
    private double weight;
 
    public Notebook(String man, String proc, int ram, int disk, double procSpeed, double screen, double weight) {
        super(man, proc, ram, disk, procSpeed); // 에러 해결!
        screenSize = screen;
        this.weight = weight;
    }
    // Overriding
    public String toString() {
        String result = "Manufacturer: " + manufacturer +
        "\nCPU: " + processor +
        "\nRAM: " + ramSize + " megabytes" +
        "\nDisk: " + diskSize + " gigabytes" +
        "\nProcessor speed: " + processorSpeed + " gigahertz" + 
        "\nScreen Size: " + screenSize + " inches" + // 이 부분이 추가됨
        "\nWeight: " + weight + " kg";                 // 이 부분이 추가됨
        return result;
        }
    
    
}
 
cs

 

이때, 주의할 점이 하나 있다. superclass인 Computer class에서 일반적을 데이터 멤버들은 private로 하고, get/set 메서드를 통해 다른 클래스에서 접근할 수 있도록 하는데, 이 경우에 subclass인 Notebook class에서는 상속받은 그 데이터 멤버들을 클래스 객체를 생성하지 않고도 사용할 수 있을까?

private이면 다른 클래스에서 접근하지 못하도록 하는 것인데... 상속을 받은 것은 다른 클래스이긴 하지만 부모 자식 관계이기도 하고...?

정답은 "일반적인 다른 클래스와 동일하게 접근할 수 없다" 이다. 그래서 오버라이딩을 할 때에 부모클래스에서 private처리된 데이터 멤버들은 자식 클래스에서 자신의 데이터 멤버처럼 똑같이 사용할 수 없음에 주의하자. 이게 규칙이다.

 

이때 등장하는 것이 protected 이다. 

어떤 변수가 private이면 다른 어떤 클래스에서도 그 변수에 원칙적으로 접근할 수 없다. 만약, 상속의 관계라고 하더라도 마찬가지이다.

하지만, 어떤 변수가 protected이면 상속 받은 클래스에서만큼은 그 변수에 접근할 수 있도록 허용해준다. 

다른 클래스라고 하기에는 애매한 느낌이 들던 자식 클래스는 부모 클래스에서 protected 처리된 데이터 멤버들에는 자신의 데이터 멤버처럼 접근이 가능한 것이다.

 

protected를 사용하지 않더라도 오버라이딩을 할 때, superclass의 데이터 멤버가 private인 경우에도 subclass에서 에러없이 사용할 수 있는 방법이 있다.

아래 코드를 살펴보자.

 

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
package section1;
 
public class Notebook extends Computer {
    private double screenSize;
    private double weight;
 
    public Notebook(String man, String proc, int ram, int disk, double procSpeed, double screen, double weight) {
        super(man, proc, ram, disk, procSpeed); // 에러 해결!
        screenSize = screen;
        this.weight = weight;
    }
    // Overriding
//    public String toString() {
//        String result = "Manufacturer: " + manufacturer +
//        "\nCPU: " + processor +
//        "\nRAM: " + ramSize + " megabytes" +
//        "\nDisk: " + diskSize + " gigabytes" +
//        "\nProcessor speed: " + processorSpeed + " gigahertz" + 
//        "\nScreen Size: " + screenSize + " inches" + // 이 부분이 추가됨
//        "\nWeight: " + weight + " kg";        &nbs

 

Theme. 다형성(Polymorphism)

 

다형성이란, 수퍼클래스 타입의 변수(참조변수)가 서브 클래스 타입의 객체를 참조할 수 있다는 것이다.

즉, 위 예제에서 Computer class는 superclass이고, Notebook class는 subclass이므로,

Computer theComputer = new Notebook("Bravo", "Intel", 4, 240, 2/4, 15.07, 5);와 같이 

Computer 타입의 변수 theComputer가 Notebook 타입의 객체를 참조할 수 있다.

하지만, 역은 성립하지 않는다.(subclass 타입의 변수가 superclass 타입의 객체를 참조할 수 없다.)

 

이때, 만약 아래와 같이 superclass와 subclass가 각각 가지고 있는 toString() 메서드를 실행한다면, 둘 중 어떤 toString()메서드가 실행될까?

System.out.println(theComputer.toString());

이에 대해

1. theComputer는 Computer(superclass) 타입의 변수이므로 Computer class의 toString() 메서드가 실행된다고 하는 것은 static binding(정적 바인딩)이라고 한다.

2. theComputer가 Notebook(subclass) 타입의 변수이므로 Notebook class의 객체 내에 존재하는 toString() 메서드가 실행된다고 하는 것은 dynamic binding(동적 바인딩)이라고 한다.

Java에서는 동적 바인딩이 일어난다.

 

Theme. static

 

클래스는 타입이다. 이전에 집이 아니라 집의 설계도라고 언급한 바 있다. 즉, 실체가 아니라는 것이다.

따라서 클래스 안에서 클래스의 데이터 필드에 데이터를 저장할 수는 없고, 클래스의 멤버 메서드를 실행할 수도 없다. 

단지 new 명령으로 해당 클래스 타입의 객체를 만든 후, 그 객체에 데이터를 저장하고, 그 객체의 멤버 메서드를 실행하는 것이다. 

 

여기에는 하나의 예외가 존재하는데 그것이 static 멤버이다.

static 멤버는 클래스 안에 실제로 존재하며(클래스 안에서 실체가 있다), 객체에는 존재하지 않는다.

 

아래 그림을 통해 자세히 설명하도록 한다.

 

 

먼저, 가장 중요한 것은 static 멤버는 class 멤버라는 것non-static 멤버는 object 멤버라는 것이다.

 

non-static 멤버부터 알아보자.

 

희미하게 적혀있는

public int t = 0;

public void print2() {

    System.out.println("t= " + t);

}

부분은 클래스 내에서 실제로 변수 t가 존재하거나 print2() 메서드를 실행할 수 있는 것이 아니다는 것을 나타낸다.

t와 print2()는 new 명령으로 Test 타입의 객체를 생성하였을 때, 그 객체 안에 존재하는 것이다.

즉, Test test1 = new Test(); 를 작성하고,

test1.t = 100;

test1.print2();

와 같이 사용해야 하는 것이다.

 

만약 Test test2 = new Test();를 통해 새로운 객체를 하나 더 만들었다면,

각각의 객체마다 서로 다른 t, print2()가 존재하는 것이다.

(각각의 t는 다른 변수이므로 t에 서로 다른 값을 저장할 수 있다.)

 

반면에, static 멤버는 class 멤버로 class안에 존재하는 것이지 생성된 객체에 존재하는 것이 아니다.

그래서 static 멤버는 하나의 클래스 안에 유일하게 존재한다. 생성된 객체마다 어떤 멤버가 존재하는 등의 일은 발생할 수 없다.

 

몇 가지 질문을 통해 static에 대해 좀 더 알아보자.

 

Q. 왜 main 메서드는 반드시 static이어야 하는가?

먼저, 자바는 클래스들의 집합임을 기억하자. 클래스 외부에 존재할 수 있는 것은 아무것도 없다.

따라서, main 메서드조차 클래스 안에 존재하는 것이다.

여기서 문제가 발생한다. 클래스 안에서는 어떠한 메서드들도 실행할 수 없는데, 그 메서드들은 어디서 실행할 수 있는가?

메서드를 실행하기 위해서는 그 메서드가 포 함된 클래스 타입의 객체를 생성하고, 그 객체 내의 메서드를 실행해야 한다. 이를 프로그램의 출발점인 main에서 실행시키고자 한다면, main 메서드는 static이어야만 한다. 

프로그램의 시작점인 main 메서드가 포함된 클래스의 객체를 만드는 것은 불가능하다. 따라서 main 메서드 만큼은 static으로 두어서 그 안에서 다른 클래스들의 객체를 생성하고, 객체 내 데이터를 사용하거나 메서드를 실행하는 등의 작업을 수행할 수 있는 것이다.

 

Q. 왜 static 메서드에서 같은 클래스의 non-static 멤버를 엑세스 할 수 없는가?

 결론부터 말하자면, static은 class의 멤버이고, non-static 멤버는 object 멤버이기 때문이다.

non-object 멤버는 object 멤버이기 때문에 클래스 내에서는 허상일 뿐이다. 객체를 생성해주고 그 객체 안에서 어떠한 값을 가지거나 메서드를 실행할 수 있다. 따라서, 어떤 클래스 내의 static 메서드에서 non-static 멤버를 엑세스할 수 없는 것이다. 

 

이를 확인할 수 있는 아래 코드를 살펴보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
package section3;
 
public class Test {
 
    static int s = 0;
    int t = 0;
 
    // 어떤 클래스 안에 있는 static 메서드 안에서
    public static void printStatic() {
        System.out.println("s= " + s); // static 멤버에 access하는 것은 문제가 없다.
        System.out.println("t= " + t); // non-static 멤버에 access할 수 없다.
    }
cs

 

그런데, 어찌보면 당연하게 넘겼을 지 모르는 아래 코드를 살펴보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package section3;
 
public class Test {
 
    static int s = 0;
    int t = 0;
 
/*
    // 어떤 클래스 안에 있는 static 메서드 안에서
    public static void printStatic() {
        System.out.println("s= " + s); // static 멤버에 access하는 것은 문제가 없다.
        System.out.println("t= " + t); // non-static 멤버에 access할 수 없다.
    }
*/
    public void printNonStatic() {
        System.out.println("s= " + s);
        System.out.println("t= " + t);
    }
cs

printNonStatic() 메서드에서 

Test 타입의 객체를 생성해 준 적도 없는데, t에 어떻게 접근할 수 있을까?

물론, Test test1 = new Test(); 와 같이 Test 타입의 객체를 생성하는 코드가 눈에 보이지는 않지만,

클래스 Test 내에 printNonStatic이라는 메서드를 생성할 때, 그 메서드는 non-static 멤버이므로 클래스가 아닌 객체 안에 존재하게 되고, 메서드가 생성되었다는 것은 곧 객체도 생성되어 있다는 것과 같다.

따라서, 메서드의 생성과 동시에 존재하는 그 객체 안에는 t도 존재할 것이므로 위와 같이 접근할 수 있는 것이다.

즉, 위 메서드에서 접근하는 t는 클래스 안에 있는 t가 아니라 객체 내의 t이다.

 

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
package section3;
 
public class Test {
 
    static int s = 0;
    int t = 0;
 
 
    // 어떤 클래스 안에 있는 static 메서드 안에서
    public static void printStatic() {
        System.out.println("s= " + s); // static 멤버에 access하는 것은 문제가 없다.
//        System.out.println("t= " + t); // non-static 멤버에 access할 수 없다.
    }
 
    public void printNonStatic() {
        System.out.println("s= " + s);
        System.out.println("t= " + t);
    }
 
    public static void main(String[] args) {
 
        s = 100// s는 static이므로 이상 없음
//        t = 100; // t는 non-static이므로 에러 발생
        printStatic(); // static
//        printNonStatic(); // non-static
        
        // non-static 멤버들을 사용하기 위해서는 객체를 생성해야 함
        Test test1 = new Test();
        test1.t = 100;
        test1.printNonStatic();
        
        
    }
 
}
 
cs

 

Q. 다른 클래스에 속한 static 멤버는 어떻게 엑세스하는가?

 

 위에서 생성한 Test class에 추가적으로 TestTest class를 생성하였다.

 간단하게 말하면, static 멤버는 class 멤버이므로 "클래스명.static멤버" 의 형식으로 접근하면 된다.

이는 마치 non-static 멤버가 object 멤버이므로 "객체를 참조하는 변수명.object멤버"의 형식으로 접근하는 것과 같다.

 

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
package section3;
 
public class TestTest {
    
    public static void main(String[] args) {
        Test test1 = new Test();
        
        test1.t = 10// non-static 멤버(객체에 접근)
        
        test1.printNonStatic(); // non-static 멤버
        
        Test.s = 100// static 멤버(클래스에 접근)
        
        Test.printStatic(); // static 멤버
        
        /* 아래 코드는 오류는 나지 않지만, 잘못된 코드
        
        // static 멤버인데, 속해 있지도 않은 객체에서 접근하려고 하고 있다.
        test1.s = 100; // static 멤버
        
        test1.printStatic(); // static 멤버
        
        */    
        
    }
    
}
 
cs

 

Q. static 메서드/필드의 용도는 무엇인가?

 물론, 자바에서 static을 필수적으로 사용해야 하는 경우는 main 메서드뿐이다.

하지만, static 멤버를 사용하는 것이 더 유용한 경우가 있다.

예컨대, 상수 혹은 클래스 당 하나만 유지하고 있으면 되는 값(혹은 객체)의 경우가 있다.

Math.PI 가 3.141592....의 값을 가질 때, 프로그램 전체에서 값을 바꿀 이유가 없기 때문에 static 멤버로 만들어 두고 사용하면 된다.

또한 순수하게 기능만으로 정의되는 메서드. 대표적인 예로 수학함수들의 경우 static을 사용하면 유용하다.

Math.abs(k), Math.sqrt(n), Math.min(a,b)와 같은 메서드들은 어느 클래스에 존재하든 독립적으로 매개변수에 따라서만 메서드의 결과가 달라지므로 static 멤버로 사용할 수 있다.

 

Theme. public

 

접근 제어: public, private, default, protected 에 대하여 간략하게 정리하면,

 

  • public: 클래스 외부에서 접근이 가능하다.
  • private: 클래스 내부에서만 접근이 가능하다.(클래서 외부에서 접근 불가능)
  • default: 동일 패키지에 있는 다른 클래스에서 접근 가능하다.
  • protected: 동일 패키지의 다른 클래스와 다른 패키지의 하위클래스에서도 접근 가능하다.

 

이중 public 과 private에 대해 좀 더 자세히 알기 위해,

"데이터 캡슐화"에 대해 먼저 알아보자.

결론부터 말하자면, 어떤 클래스 안에 있는 데이터 멤버들을 모두 private으로 만들어서 다른 클래스에서는 접근하지 못하게 만들어 놓고, 대신 필요한 경우에 접근할 수 있도록 하는 것이다. 접근할 수 있는 방법은 해당 클래스에서 public한 get/set 메서드를 제공하는 것이다.

즉, 다른 클래스에서 접근하고자 할 때, public 한 get/set 메서드를 통해 private한 데이터 멤버들에 접근하는 것이다. 그러한 메서드를 getter, setter 메서드라고도 한다.

클래스 내부에 있는 데이터가 무분별하게 접근되는 것을 방지함으로써 의도하지 않은 프로그램 오류 등을 방지하기 위해 private를 사용하고, 전형적인 객체지향 프로그래밍에서는 모든 데이터 멤버들을 private으로 만들고 필요한 경우에 getter, setter를 제공한다.

 

 

 

 

클래스는 서로 관련있는 데이터들을 하나의 단위로 묶어두기 위한 것이라고 설명할 수 있다. 하지만, 이것이 전부가 아니다.

서로 다른 데이터들뿐 아니라 그 데이터와 관련이 깊은 메서드함께 묶어둘 수 있다.

그 결과 서로 관련성이 높은 것들끼리 하나의 단위로 묶여있게 되고 이는 코드의 응집도(cohesion)를 높이게 된다.

반면, 클래스 간의 연결성은 느슨해지게 되므로 코드의 결합도(coupling)를 낮출 수 있다.

그 결과 어떤 장점이 있는가?

코드를 수정해야 할 때, main 메서드가 존재하는 클래스를 수정하는 것을 최소화하고, 각각의 클래스들이 나뉘어 있으므로 해당 클래스에서 수정해주면 되기 때문에 유지보수가 용이하다는 장점이 있다.

 

 

클래스에 포함된 데이터와 메서드는 어떤 의미를 가질까?

객체지향 프로그래밍에서 객체란 "데이터"와 "메서드"의 집합이라고 말할 수 있다. 데이터는 객체의 "정적" 속성을 표현하며, 메서드는 객체의 "동적" 속성(기능)을 표현한다.

예컨대, 클래스를 자전거에 비유했을 때,  자전거는 "모양, 무게, 크기, 브랜드" 등의 정적 속성(데이터)과 "달린다, 정지한다, 뒤로간다" 등의 기능을 가지게 된다.

 

Theme. 생성자

 

클래스 안에 그 클래스와 동일한 이름을 가지며 return 타입이 없는 특별한 메서드를 둘 수 있다. 이것을 생성자(constructor)라고 부른다.

생성자는 new 명령으로 객체가 생성될 때 자동으로 실행된다. 주 목적은 객체의 데이터 필드의 값을 초기화하는 것이다.

생성자가 반드시 매개변수를 받아야하는 것은 아니다. 

 

 

이전 게시물에서 직사각형에 대한 정보를 입력받아 면적을 기준으로 정렬하여 출력한 예제를 생성자를 활용하여 보다 효율적으로 코드를 작성해보고자 한다.

 

1. 왼쪽 위 점을 나타내는 MyPoint2 class

 

1
2
3
4
5
6
7
8
9
10
11
12
package section2;
 
public class MyPoint2 {
    public int x;
    public int y;
    
    public MyPoint2(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
 
cs

 

2. 직사각형 하나에 대한 데이터와 메서드를 나타내는 MyRectangle2 class

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package section2;
 
public class MyRectangle2 {
    public MyPoint2 lu;
    public int width;
    public int height;
    
    public MyRectangle2(int x, int y, int w, int h) {
        lu = new MyPoint2(x, y);
        width = w;
        height = h;
    }
    
    public int calArea() {
        return width * height;
    }
    
    public String toString() {
        return "(" + lu.x + ", " + lu.y + ") " + width + " " + height + " ";
    }
    
    
}
 
cs

 

3. main class

 

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
package section2;
 
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
 
public class Code05 {
 
    static MyRectangle2[] rects = new MyRectangle2[100]; // 사각형들의 배열
    static int n = 0// 배열에 저장된 사각형의 개수 n
 
    public static void main(String[] args) {
 
        try {
            Scanner sc = new Scanner(new File("data.txt"));
 
            while (sc.hasNext()) {
                int x = sc.nextInt();
                int y = sc.nextInt();
                int w = sc.nextInt();
                int h = sc.nextInt();
 
                rects[n] = new MyRectangle2(x, y, w, h);
                n++;
            }
            sc.close();
        } catch (FileNotFoundException e) {
            System.out.println("No data file.");
            System.exit(1); // 프로그램 종료
 
        }
 
        bubbleSort();
 
        for (int i = 0; i < n; i++) {
            System.out.println(rects[i].toString());
        }
 
    }
 
    public static void bubbleSort() {
        for (int i = n - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if (rects[j].calArea() > rects[j+1].calArea()) {
                    MyRectangle2 tmp = rects[j + 1];
                    rects[j + 1= rects[j];
                    rects[j] = tmp;
                }
            }
        }
 
    }
 
}
 
cs

 

아래 예제를 해결해보자.

1. MyPoint2 class

 

1
2
3
4
5
6
7
8
9
10
11
12
package section2;
// 한 점에 대한 class
public class MyPoint2 {
    public int x;
    public int y;
    
    public MyPoint2(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
 
cs

 

 

2. OrthoLine class

 

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
package section2;
// 평면 상의 하나의 수직(수평) 선분을 나타내는 class
public class OrthoLine {
 
    // 선분을 만들기 위한 두 점(선분의 양 끝점)을 선언
    public MyPoint2 u;
    public MyPoint2 v;
    
    public OrthoLine( MyPoint2 p, MyPoint2 q) {
        u = p;
        v = q;
        // 수직 선분의 경우 위쪽이 u, 아래쪽이 v
        // 수평 선분의 경우 왼쪽이 u, 오른쪽이 v가 되도록 해주었다.
        if(p.x > q.x || (p.x == q.x && p.y > q.y)) {
            swap();
        }
    }
    
    public void swap() {
        MyPoint2 tmp = u;
        u = v;
        v = tmp;
    }
    
    public OrthoLine( int x1, int y1, int x2, int y2) {
        u = new MyPoint2(x1, y1);
        v = new MyPoint2(x2, y2);
    }
    
    // 두 선분이 교차하는가?(수직, 수평 선분만 존재한다고 가정)
    // 메서드를 OrthoLine class에 정의하였으므로 다른 선분과의 교차여부만 파악하면 된다.
    public boolean intersects( OrthoLine other ) {
        // 내가 수직, other는 수평 선분
        if(isVertical() && !other.isVertical()) {
            return (u.x > other.u.x && u.x < other.v.x && u.y < other.u.y && v.y > other.u.y); // true면 교차하는 것
        }
        // 내가 수평, other는 수직 선분
        else if(!isVertical() && other.isVertical()) {
            return (u.x < other.u.x && v.x > other.u.x && u.y > other.u.y && u.y < other.v.y);
        }
        // 둘다 수직, 둘다 수평 선분
        else {
            return false// 두 선분은 만나지 않는다.
        }
        
    }
    // 수직 선분인지 여부
    public boolean isVertical() {
        return (u.x == v.x);
    }
    
    
    
    
    
    
}
 
cs

 

 

3. OrthoPolygon class

 

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
package section2;
 
// 하나의 직교다각형을 나타내는 class
public class OrthoPolygon {
    public int n = 0;
    public MyPoint2[] vertices;
 
    public OrthoPolygon(int k) {
        vertices = new MyPoint2[k]; // 꼭짓점 개수가 k인 다각형
    }
 
    // 꼭짓점을 추가
    public void addVertex(int x, int y) {
        vertices[n++= new MyPoint2(x, y);
    }
 
    // x좌표 중 가장 큰 x좌표를 구하는 메서드
    public int maxX() {
        int max = 0;
        for (int i = 0; i < n; i++) {
            if (vertices[i].x > max) {
                max = vertices[i].x;
            }
        }
        return max;
    }
 
    // 어떤 점이 다각형의 내/외부에 있는지 판단
    public boolean contains(MyPoint2 p) {
        OrthoLine arrow = new OrthoLine(p, new MyPoint2(maxX() + 1, p.y)); // 오른쪽 방향 수평선
        int count = 0;
        for (int i = 0; i < n; i++) {
            OrthoLine edge = new OrthoLine(vertices[i], vertices[(i + 1) % n]); // i가 n-1일 때는 다시 첫 번째 점(vertices[0])과
                                                                                // 연결돼야 하므로
            if (arrow.intersects(edge)) {
                count++;
            }
        }
        return (count % 2 == 1); // 교점의 개수가 홀수면 내부에 있는 것(짝수면 외부)
    }
}
 
cs

 

 

4. Code10

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
package section2;
 
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
 
public class Code10 {
 
    public static void main(String[] args) {
        try {
            Scanner sc = new Scanner(new File("data.txt"));
            // 꼭짓점의 개수 입력받기
            int n = sc.nextInt();
            
            OrthoPolygon thePolygon = new OrthoPolygon(n);
            // 꼭짓점 좌표 순차적으로 입력 받기
            for (int i = 0; i < args.length; i++) {
                thePolygon.addVertex(sc.nextInt(), sc.nextInt());
            }
            // 테스트 할 점의 좌표 입력 받기
            MyPoint2 thePoint = new MyPoint2(sc.nextInt(), sc.nextInt());
            sc.close();
            if(thePolygon.contains(thePoint)) {
                System.out.println("내부에 위치!");
            }
            else {
                System.out.println("외부에 위치!");
            }
            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            System.exit(1);
        } 
        
    }
 
}
 
cs

 

끝.

Theme. 클래스, 참조변수, 객체의 관계

 

클래스는 type이다.

클래스에 대해 비유적으로 얘기해보자면, 

어떤 집을 짓는다고 할때, 클래스는 마치 하나의 설계도와 같다고 말할 수 있다. 우리가 설계도 안에서 가구를 놓고 살 수 없듯이  설계도는 집을 만들기 위한 도면일 뿐, 실제로 존재하는 집은 아니다.

마찬가지로 클래스 자체에 어떤 데이터를 저장하거나, 클래스 안에 있는 데이터를 읽거나 등등을 하는 것이 아니라 클래스는 객체나 참조변수 등을 만들어 내기 위해 그 객체나 참조변수의 타입을 정의하는 등의 역할을 하는 하나의 설계도일 뿐이다. 반면에 객체는 설계도가 아니라 이를 통해 지어진 집이라고 표현할 수 있다. 

클래스 vs 참조변수, 객체의 구도를 나눈다고 했을 때, 참조변수나 객체가 실체가 되고, 클래스는 허상이라고 생각할 수 있다. 

Person a = new Person(); 에서 

Person 타입의 객체를 new 연산자를 통해 만들었고, 그 객체의 주소를 저장하는(객체를 가리키는) 참조변수 a가 존재한다. 

이때, new 연산자를 통해 생성된 객체 그 자체는 이름이 없으므로 이때 참조변수가 필요한 것이다.

 

 

Theme. 객체와 참조변수의 사용시 주의할 부분

 

어떤 클래스의 객체를 생성하고, 객체의 참조변수를  통해 객체에 접근하는 구조에 대해 이해를 하고 있더라도 코드를 작성하는 과정에서 놓칠 수 있는 부분이 있다. 그 놓칠 수 있는 부분에 대해 아래 예제와 함께 알아보도록 하자.

 

 

위 예제를 해결하기 위해,  아래와 같이 클래스를 생성하였다.1. MyPoint1.java - x좌표와 y좌표를 선언한 클래스          public class MyPoint1 {
        public int x;
        public int y;
    }

 

2. MyRectangle1.java - 어떤 직사각형에 대한 정보를 나타낸 클래스

 

1
2
3
4
5
6
7
package Section1;
 
public class MyRectangle1 {
    public MyPoint1 lu; // 왼쪽 위의 꼭짓점의 좌표(left-up)
    public int width; // 너비
    public int height; // 높이
}
cs

이때, 주의할 것은 lu라는 변수는 MyPoint1 타입이다. 

 

3. Code05.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
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
package Section1;
 
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
 
public class Code05 {
 
    static MyRectangle1[] rects = new MyRectangle1[100]; // 사각형들의 배열
    static int n = 0// 배열에 저장된 사각형의 개수 n
 
    public static void main(String[] args) {
 
        try {
            Scanner sc = new Scanner(new File("data.txt"));
 
            while (sc.hasNext()) {
                rects[n] = new MyRectangle1(); // 배열의 한 칸이 참조할 객체를 생성
                rects[n].lu = new MyPoint1(); 
 
                rects[n].lu.x = sc.nextInt(); // x 좌표
                rects[n].lu.y = sc.nextInt(); // y 좌표
                rects[n].width = sc.nextInt(); // 너비
                rects[n].height = sc.nextInt(); // 높이
                n++;
            }
            sc.close();
        } catch (FileNotFoundException e) {
            System.out.println("No data file.");
            System.exit(1); // 프로그램 종료
 
        }
 
        bubbleSort();
 
        for (int i = 0; i < n; i++) {
            System.out.println(rects[i].lu.x + " " + rects[i].lu.y + " " + rects[i].width + " " + rects[i].height);
 
        }
 
    }
 
    public static void bubbleSort() {
        for (int i = n - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if (calArea(rects[j]) > calArea(rects[j + 1])) {
                    MyRectangle1 tmp = rects[j + 1];
                    rects[j + 1= rects[j];
                    rects[j] = tmp;
                }
            }
        }
 
    }
 
    // 사각형의 면적을 구하는 함수 calArea
    public static int calArea(MyRectangle1 r) {
        return r.width * r.height;
    }
}
 
cs

 

사각형의 면적을 구하고 이를 정렬하는 것은 간단한 문제이니 생략하도록 하고,

while문을 자세히 살펴보도록 한다.

 

1
2
3
4
5
6
7
8
9
10
while (sc.hasNext()) {
                rects[n] = new MyRectangle1(); // 배열의 한 칸이 참조할 객체를 생성
                rects[n].lu = new MyPoint1(); 
 
                rects[n].lu.x = sc.nextInt(); // x 좌표
                rects[n].lu.y = sc.nextInt(); // y 좌표
                rects[n].width = sc.nextInt(); // 너비
                rects[n].height = sc.nextInt(); // 높이
                n++;
            }
cs

 

MyRectangle1[] rects = new MyRectangle1[100]; 와 같이

먼저, rects라는 참조변수가 가리키는 객체는 MyRectangle1 타입의 배열이다. 

즉, rects라는 참조변수는 "배열"을 가리킨다.

 

rects[n] 또한 MyRectangle1 타입의 참조변수로 배열의 한 "칸"에 해당한다. 

즉, 배열의 각 칸은 rects[n]이라는 참조변수를 통해 객체에 접근한다.

 

처음 배열을 선언할 때, 참조변수 rects가 MyRectangle1의 객체를 참조하도록 하였지만,

참조변수 rects[n]은 아직 그러한 부분이 작성되지 않았고, 이를 위해 while문에 

rects[n] = new MyRectangle1(); // 배열의 한 칸이 참조할 객체를 생성
rects[n].lu = new MyPoint1(); 

과 같이 코드를 작성한 것이다. 

rects[n].lu의 부분은 MyRectangle1의 객체를 가리키는 것이 아니라, MyPoint1의 객체를 가리키고, 그 안에 존재하는 x,y 좌표의 값에 접근하기 위해 작성되었다. 

( main 메서드  -----> MyRectangle1 클래스  -----> MyPoint1 클래스)

 

 

결론적으로, 참조변수가 어떤 값을 참조하도록 할 때, 그 참조변수가 참조할 수 있는 객체가 선언되어 있는지를 반드시 확인하고 그렇지 않다면 선언해주도록 하자!

Theme. JDBC(Java DataBase Connectivity)란?

 

RDBMS(MySql, Oracle 등) 또는 DBMS에서 작성된 sql문을 실행함으로써 DB에 접근할 수 있는 것은 누구나 알고 있다.

MySql을 예로 들자면,  MySql Workbench에서 sql문을 작성하면서 MySql과 연결/인증, 문장실행, 결과패치 등을 할 수 있다.

이는 다시 말하면, sql문을 작성할 줄 알면 DB에 접근할 수 있다는 것이다.

물론, sql문을 작성할 줄 알아야만 DB에 접근할 수 있는 것은 아니다.

예를 들어, 개발자가 만든 UI를 이용하는 client는 sql문을 작성할 줄도 모르고, 작성해야할 이유도 없을 것이다.

client가 회원목록에서 어떤 회원에 대한 정보를 달라고 하면, 해당 UI를 통해 sql문이 작성되도록 만들면 되기 때문이다. 

Java 프로그래머로서 DB를 이용하려는 사용자의 요구에 부응하는 sql문을 작성해주고, 이를 실행시켜주면 될 것이다.

 

client의 요구를 처리해줄 수 있는 sql문이 작성이 되고, 작성된 sql문과 DB를 연결하기 위해 필요한 것은 DB API이다. 

이때, DB API에 대해 고려해야할 사항이 있다. MySql, Oracle과 같은 RDBMS들이 모두 같은 메서드, 문법을 사용하지 않는다는 것이다. 이로 인해 sql문을 각각에 연결하기 위 DB API가 모두 달라야 한다.

똑같은 sql문을 실행한다고 해도 어떤 (R)DBMS를 쓰냐에 따라 DB API가 달라지게 되고 이는 굉장히 불편할 것이다. DBMS가 바뀌면 새로운 DBMS에 연결하기 위한 DB API에 대해 알아야 하기 때문이다.

 

이때 등장한 것이 바로 "JDBC"이다. RDMBS에 연결하기 위한 모든 코드들은 다 단일화된 JDBC안에 모여있다고 이해하면 쉽다. 이를 통해 연결하고자 하는 RDBMS에 따라 DB API에 대해 알아야 할 필요가 없다. 이제 각각의 DB API들은 JDBC의 Driver가 된다.(JDBC Driver)

 

Theme. JDBC 사용방법

기본적인 코드는 아래의 "순서대로" 사용된다.

1. 드라이버 로드

2. 연결 생성

3. 문장(sql문) 실행

4. 결과집합 사용

 

각각에 대해서 설명하기 전에,

JDBC를 사용하는 과정을 간단하게 설명하자면, UI를 만들고, UI를 통해 사용자의 요구를 파악하며, 그 요구에 따라 sql문을 작성하고 실행하여 DB를 사용하는 것이다. 아래 그림을 살펴보자.

다시 돌아와서 기본적인 코드들을 작성해보면,

1. JDBC 드라이버 로드하기

MySql을 사용하는 경우에는 다음과 같이 작성하면 된다.

Class.forName("com.mysql.cj.jdbc.Driver");

이때, 로드한다는 것의 의미는 클래스를 객체화한다는 의미이다.

그 결과 메모리에 드라이버가 올라가게 될 것이다.

 

2. 연결 생성하기

연결 객체를 얻는 것이다.

Connection conn = DriverManager.getConnection(url, user, password);

예를들면, Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "ABC", "1111");

 

3. sql문 실행 도구 생성하기

Statement st = con.createStatement();

이를 

PreparedStatement psmt = conn.prepareStatement(sql문); 으로 사용할 수 있다.(이 방법으로 코드를 작성하였다)

 

4. 실행 결과를 저장하는 객체 생성하기(결과 집합을 패치)

ResultSet rs = st.executeQuery(sql); 

이는 select, insert, update, delete 등에 따라 사용하는 메서드가 다르다.

// psmt.excuteQuery() : select

// psmt.excuteUpdate() : insert, update, delete ..

 

그림을 통해 과정을 살펴보자.

0. JDBC 사용 전

 

1. 드라이버 로드

Class.forName("com.mysql.cj.jdbc.Driver");

2. 연결 생성

Connection conn = DriverManager.getConnection(url, user, password);

 

 

3. 실행 도구 생성

PreparedStatement psmt = conn.prepareStatement(sql문);

sql문이 실행된다.

 

4. 결과 담을 객체 생성(결과 집합 생성)

ResultSet rs = st.executeQuery(sql); 

sql문이 실행되고 나면 다음과 같은 결과 집합(테이블)이 생성된다. 이는 레코드의 집합이다.

 

서버에서는 결과집합을 사용자에게 한번에 다 돌려주는 것이 아니다. 

결과 집합은 서버에 있는 채로, client는 레코드 단위로 하나씩 받게 된다. 이때 레코드를 가리키는 위의 화살표가 커서이다.

처음 커서는 Before of File에 위치하게 된다.

이때, rs.next(); 가 실행되면, 아래와 같이 커서가 이동하면서 그 레코드를 client에게 전달하게 된다.

이 커서가 End of File에 위치하게 되면 결과 집합을 모두 읽은 것이다.

 

Theme. Insert, select, update, delete문 작성하기

1. DBConnection.java 파일

 - 드라이버를 로드하는 것과 연결 생성하는 과정을 DBConnection이라는 클래스 안에 메서드로 만들어 두었다.

 - initConnection()이라는 메서드를 만들어서 드라이버를 로드한다.

 - getConnection()이라는 메서드를 만들어서 연결을 생성한다.

 

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
package db;
 
import java.sql.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
 
public class DBConnection {
 
    public static void initConnection() { // static이니까 언제든지 불러쓸 수 있다.
        
        try {
            Class.forName("com.mysql.cj.jdbc.Driver"); // 드라이버 로드하기
            System.out.println("Driver Loading Success");
        } catch (ClassNotFoundException e) {            
            System.out.println("DB Driver를 찾지 못했습니다");
            e.printStackTrace();
        }    
        
    }
    
    public static Connection getConnection() {
        
        Connection conn = null;    // Connection 객체를 생성
        try {
            // 연결할 url, 데이터베이스 ID, 데이터베이스 PW
            conn = DriverManager.getConnection(url, id, pw); // 실행하는 환경에 따라 입력
            System.out.println("Connection Success");            
        } catch (SQLException e) {            
            System.out.println("db을 연결하지 못했습니다");
            e.printStackTrace();
        }    
        return conn;
    }
    
    
}
 
cs

2. db.sql 파일

 - id, name, age, joindate라는 컬럼 4가지로 테이블을 만들어 두었다.

 - 테이블명은 user이다.

 

3. User.java 파일

client의 정보를 나타내는 객체를 생성하여 사용하고자 만들었다.

 

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
package dto;
 
import java.io.Serializable;
 
public class User implements Serializable{
 
    private String id;
    private String name;
    private int age;
    private String joindate;
    
    public User() {}
 
    public User(String id, String name, int age, String joindate) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
        this.joindate = joindate;
    }
 
    
    public String getId() {
        return id;
    }
 
    public void setId(String id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
 
    public String getJoindate() {
        return joindate;
    }
 
    public void setJoindate(String joindate) {
        this.joindate = joindate;
    }
    
    @Override
    public String toString() {
        return "UserDto [id=" + id + ", name=" + name + ", age=" + age + ", joindate=" + joindate + "]";
    }
 
    
    
}
 
cs

4. DBClose.java

 실행 중인 객체들을 종료하도록 한다. 주의할 것은 실행된 순서와 반대로 close해줘야 한다는 것이다.

 

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
package db;
 
import java.sql.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
 
 
public class DBClose {
 
    public static void close(Connection conn, Statement psmt, ResultSet rs) {
        //사용순서와 반대로 close 함
        try {
            if(rs != null) {
                rs.close();
            }
            if(psmt != null) {
                psmt.close();
            }
            if(conn != null) {    
                conn.close();
            }
        } catch (SQLException e) {                
            e.printStackTrace();
        }            
    }
    
    
    
}
 
cs

 

5. JdbcTest.java

Insert, select, update, delete문을 작성한다.

 

< Insert >

 

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
// insert
    public boolean insert(String id, String name, int age) {
        
        // 쿼리문 작성(sql 문장)
        String sql = " insert into user(id, name, age, joindate) "
                + "    values(?, ?, ?, now())  ";
        
        Connection conn = null// 데이터베이스와 연결을 위한 객체
        PreparedStatement psmt = null// sql문을 데이터베이스에 보내기 위한 객체
        
        int count = 0;
        
        try {
            conn = DBConnection.getConnection();
            
            psmt = conn.prepareStatement(sql); // sql문 저장
            // DB는 인덱스가 1부터 시작. 위 ? 매개변수에 값을 지정
            // pstmt.set<데이터타입>(? 순서(1부터시작), 값)
            psmt.setString(1, id);
            psmt.setString(2, name);
            psmt.setInt(3, age);
            
            // executeUpdate(): 반환 값은 해당 sql문 실행에 영향을 받는 행 수(레코드 건 수)
            // 그렇게 영향을 받은 행 수를 변수 count에 저장하는 것이다.
            count = psmt.executeUpdate(); 
            
            // psmt.excuteQuery() : select
            // psmt.excuteUpdate() : insert, update, delete ..
            
            System.out.println("성공적으로 추가되었습니다");
            
        } catch (SQLException e) {
            System.out.println("추가되지 않았습니다");
            e.printStackTrace();
        } finally {
            DBClose.close(conn, psmt, null);
        }
        
        return count > 0 ? true : false;
    }
cs

 

< select >

 

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
80
81
// select
    // 1개의 데이터 취득(예를 들면, 1개의 object 취득)
    public User search(String id) {
        
        String sql = " select id, name, age, joindate"
                + "    from user "
                + "    where id = ? ";
        
        Connection conn = null// 데이터 베이스와 연결을 위한 객체
        PreparedStatement psmt = null// SQL 문을 데이터베이스에 보내기위한 객체
        ResultSet rs = null// SQL 질의에 의해 생성된 테이블을 저장하는 객체
        
        User user = null;
        
        try {
            conn = DBConnection.getConnection();
            
            psmt = conn.prepareStatement(sql);
            
            psmt.setString(1, id); // where id = ?의 ?에 들어갈 id
            
            
            // executeQuery(): ResultSet 객체의 값을 반환
            // executeQuery 메소드는 데이터 베이스에서 가져온 데이터를 resultSet에 담아 그 resultSet을리턴한다.
            rs = psmt.executeQuery();
            
            // rs.next() 를 이용해서 커서를 이동
            if(rs.next()) {
                String _id = rs.getString("id"); // 1이라고 적어도 된다. (select문에서 id, name, age, joindate 순서대로 나왔으므로 1,2,3,4)
                String _name = rs.getString("name");
                int _age = rs.getInt("age");
                String _joindate = rs.getString("joindate");
                
                user = new User(_id, _name, _age, _joindate);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBClose.close(conn, psmt, rs);
        }
        
        return user;
        
    }
    
    // 다수의 데이터 취득 
    public List<User> select() {
        
        String sql = " select id, name, age, joindate "
                + "    from user ";
        
        Connection conn = null;
        PreparedStatement psmt = null;
        ResultSet rs = null;
        
        List<User> list = new ArrayList<User>();
        
        try {
            conn = DBConnection.getConnection();
            psmt = conn.prepareStatement(sql);
            rs = psmt.executeQuery();
        
            while(rs.next()) {
                String _id = rs.getString("id");
                String _name = rs.getString("name");
                int _age = rs.getInt("age");
                String _joindate = rs.getString("joindate");
                
                User user = new User(_id, _name, _age, _joindate);
                list.add(user);
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            DBClose.close(conn, psmt, rs);
        }
        
        return list;
        
    }
cs

 

< update >

 

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
// update
    
    public boolean update(String id, String name, int age) {
        
        String sql = " update user "
                + "    set name=?, age=? "
                + "    where id=? ";
        
        Connection conn = null;
        PreparedStatement psmt = null;
        
        int count = 0;
        
        try {
            conn = DBConnection.getConnection();
            psmt = conn.prepareStatement(sql);
            
            psmt.setString(1, name);
            psmt.setInt(2, age);
            psmt.setString(3, id);
            
            count = psmt.executeUpdate();
            
        } catch (SQLException e) {
            
            e.printStackTrace();
        } finally {
            DBClose.close(conn, psmt, null);
        }
        
        return count > 0 ? true : false// return count>0;
        
    }
cs

 

< delete >

 

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
// delete
    public boolean delete(String id) {
        
        String sql = " delete from user where id=?";
                
        Connection conn = null;
        PreparedStatement psmt = null;
        
        int count = 0;
        
        try {
            conn = DBConnection.getConnection();
            psmt = conn.prepareStatement(sql);
            
            psmt.setString(1, id);    
            count = psmt.executeUpdate();
            
        } catch (SQLException e) {
            
            e.printStackTrace();
        } finally {
            DBClose.close(conn, psmt, null);
        }
        
        return count > 0 ? true : false// return count>0;
        
    }
cs

 

이 메서드들을 호출하여 DB에 값을 입력, 검색, 수정, 삭제 등의 작업을 수행할 수 있다.

 

 

 

 

 

 

 

 

 

 

Singleton에 대해 자세히 설명하는 것은 다른 게시물에서 하도록 하고, 

간단하게 구현하는 것에 이 글의 목적을 둔다.

코드에서 주석처리한 부분은 singleton을 사용하지 않았을 때, getter와 setter로 값을 넘겨받아야 하는 것을 나타낸 것이다.

 

각 클래스는 number, name이라는 필드(data member 또는 멤버변수)를 가지고 있다.

 

1. MyClass.java, YouClass.java, Singleton.java안에서 number와 name의 값을 서로 넘겨받는다.

2. MainClass.java가 존재한다. 여기서 값들을 확인해보자.

3. 모두 확인해보고 HeClass.java를 만들어 Singleton에 접근해보자.

즉, 

변수 number는

MyClass -> Singleton -> YouClass의 과정으로 접근하게 되고,

변수 name은

YouClass -> Singleton -> MyClass의 과정으로 접근하게 된다.

이후에 생성된 HeClass는 Singleton으로부터 변수 name과 number에 접근한다.

 

 

먼저, MyClass.java는 다음과 같다.

특징은 number에 대한 값은 가지고 있으나, name에 대한 값은 가지고 있지 않다는 것이다.

 

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
package cls;
 
import single.Singleton;
 
public class MyClass {
    
    private int number;
    private String name;
    
    public MyClass() {
        number = 256;
    }
    
    /*
    public int getNumber() {
        return number;
    }
    */
 
    public void mySetNumberMethod() {
        Singleton si = Singleton.getInstance();
        si.number = number; // MyClass의 number의 값을 si.number에 저장
    }
    
    public void myGetNameMethod() {
        Singleton si = Singleton.getInstance();
        name = si.name; 
    }
 
    @Override
    public String toString() {
        return "MyClass [number=" + number + ", name=" + name + "]";
    }
    
    
}
 
cs

 

 

YouClass.java는 다음과 같다.

특징은 number에 대한 값은 가지고 있지 않고, name에 대한 값은 가지고 있다는 것이다.

 

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
package cls;
 
import single.Singleton;
 
public class YouClass {
    
    private String name = "홍길동";
    private int number;
 
    /*
    public void setNumber(int number) {
        this.number = number;
    }
    */
    
    public void youGetNumberMethod() {
        Singleton si = Singleton.getInstance();
        number = si.number; 
    }
    
    public void youSetNameMethod() {
        Singleton si = Singleton.getInstance();
        si.name = name; // YouClass의 name의 값을 si.name에 저장
    }
    
    
 
 
    @Override
    public String toString() {
        return "YouClass [name=" + name + ", number=" + number + "]";
    }
    
    
    
}
 
cs

 

Singleton.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
package single;
 
public class Singleton {
 
    private static Singleton single = null;
    public int number;
    public String name;
    
    private Singleton() {} // 싱글톤 생성자에 접근 불가능
    
    // 아래 코드 덕분에 싱클톤의 객체를 생성하고, 그 객체의 주소를 담은 여러 변수가 있을 때, 모두 하나의 객체의 주소를 저장하게 된다. 
    public static Singleton getInstance() {
        if(single == null) {
            single = new Singleton();
        }
        return single;
    }
}
 
// 싱글톤 클래스를 만드는 방법
// 1. 생성자를 private으로 만든다.
// 2. 이 싱글톤 클래스를 반환유형으로 한 정적 메서드를 작성한다.
// 3. 일반 클래스의 경우 클래스를 정의할 때 생성자를 사용하는 반면, 싱글톤 클래스의 경우 getInstance() 메서드를 사용한다.
 
// 일반 클래스의 경우 class a = new class(); 이런식으로 객체를 생성하는것과 달리 싱글톤 클래스의 경우 getInstance() 메소드를 호출하여 객체를 생성
// 싱클톤의 객체를 생성하고, 그 객체의 주소를 담은 여러 변수가 있을 때, 모두 하나의 객체의 주소를 저장하게 된다. 
cs

 

HeClass.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
package cls;
 
import single.Singleton;
 
public class HeClass {
 
    private int number;
    private String name;
    
    public HeClass() {
        
    }
    
    public void func() {
        Singleton si = Singleton.getInstance();
        number = si.number;
        name = si.name;
    }
 
    @Override
    public String toString() {
        return "HeClass [number=" + number + ", name=" + name + "]";
    }
 
    
    
}
 
cs

 

MainClass.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
package main;
 
import cls.HeClass;
import cls.MyClass;
import cls.YouClass;
import single.Singleton;
 
public class MainClass {
    public static void main(String[] args) {
        
        MyClass mycls = new MyClass();
        YouClass youcls = new YouClass();
        
//        int number = mycls.getNumber();
//        youcls.setNumber(number);
        
//        System.out.println(youcls.toString());
                
        // singleton을 사용한 경우
        // number
        mycls.mySetNumberMethod(); // mycls의 값을 singleton이 받고,
        youcls.youGetNumberMethod(); // singleton에서 youcls로 넘겨준다.
        System.out.println(youcls.toString());
        
        // name
        youcls.youSetNameMethod(); // youcls의 값을 singleton이 받고,
        mycls.myGetNameMethod(); // singleton에서 mycls로 넘겨준다.
        System.out.println(mycls.toString());
        
        
        // 새로운 클래스를 생성했을 때 접근
        HeClass hc = new HeClass();
        hc.func();
        System.out.println(hc.toString());
        
        
    }
}
 
cs

 

 

+ Recent posts