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

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

그 결과 서로 관련성이 높은 것들끼리 하나의 단위로 묶여있게 되고 이는 코드의 응집도(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. 함수

1. return 값이 있는 함수

 

if(k<2) { return false } 부분은 1이 소수로 포함되지 않도록 하는 부분!

 

2. return 값이 없는 함수

 

사용자로부터 n개의 정수를 입력받은 후 오름차순으로 정렬(sort)하여 출력해라.

Bubble 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
package CH1;
 
import java.util.Scanner;
 
public class Code18 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] data = new int[n];
        for (int i = 0; i < n; i++) {
            data[i] = sc.nextInt();
        }
        sc.close();
        
        bubblesort(n, data);
        System.out.print("Sorted data: ");
        for (int i = 0; i < n; i++) {
            System.out.print(data[i] + " ");
        }
        
    }
    
    static void bubblesort(int n, int[] data) {
        for (int i = n-1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if(data[j] > data[j+1]) {
                    int tmp = data[j];
                    data[j] = data[j+1];
                    data[j+1= tmp;
                }
            }
        }
        return;
    }
}
 
cs

 

 이때, swap하는 과정을 함수로 나타내어 호출하고자 아래와 같은 코드를 작성하면,

실행시켜보면, 정렬이 되지 않는다. 무엇이 문제일까?

"값에 의한 호출"에 대해 이해하면 그 이유를 알 수 있다.

매개변수의 전달과정에서

호출문의 data[j], data[j+1]은 각각 data 배열에 존재하는 값(배열의 한 칸에 해당)으로 actual parameter이다.

호출된 메서드의 a, b는 formal parameter이다.

"값에 의한 호출"이란 결론적으로 말하면 actual parameter와 formal parameter는 별개의 변수라는 것(메모리 영역에서 서로 다른 영역을 차지한다)이다.

다만, 메서드를 호출하는 순간(호출문이 실행되는 순간) actual parameter 값이 formal parameter에 복사가 된다.

즉, 값만 복사가 되는 것이지 서로 별개의 변수이므로 a,b를 스왑한다고 해서 data[j], data[j+1]이 스왑되는 것은 아니다. 

복사본들끼리 스왑한다고 원본들이 스왑되는 것은 아니다라고 이해해도 좋다.

 

그런데!!

swap메서드를 추가하지 않고, bubblesort 메서드만을 이용하여 코드를 작성했을 때에도,

호출문: bubblesort(n, data);

호출된 메서드: static void bubblesort(int n, int[] data); ...에서

"값에 의한 호출"에 의해 정렬이 올바르게 되지 않았어야 하는 것이 아닌가? 라는 의문이 들 수 있다.

하지만, 정렬은 올바르게 된다. 

무슨 차이점이 있는가? 

 

답은 "배열"에 있다. 정확히 말하자면, 배열만이 예외는 아니다.

즉, 기본형(primitive type)의 매개변수(8가지)는 호출된 메서드에서 값을 변경하더라도 호출한 쪽에 영향을 주지 못한다. 이것은 "값에 의한 호출"이기 때문이다.

하지만, 배열은 참조형 변수다. 따라서, "값에 의한 호출"에 영향을 받지 않는다.

배열의 값은 호출된 메서드에서 변경하면 호출한 쪽에서도 변경된다.

 

txt파일에 몇 명의 사람들이 존재하는지 모르므로 배열의 길이를 1000으로 둔 것.

하지만, 위와 같이 코드를 작성하면 FileNotFoundException이 발생한다. try catch문을 작성하도록 한다.

위 코드에서의 return은 main 메서드를 종료하게 되는데, 이는 곳 프로그램 종료를 의미하므로 System.exit(0)으로 대체할 수 있다.

여러 개의 클래스에 사용되는 변수가 존재하므로, 그 변수들을 멤버변수로 선언한다.

위 코드의 name[j] > name[j+1] 부분에서 에러가 발생한다.

각각 String의 값이기 때문인데,

문자열 간의 사전식 순서를 검사하기 위한 compareTo메서드는 1,0,-1 3가지 값을 반환하게 된다.

즉, name[j].compareTo(name[j+1]) < 0 이면, name[j]가 name[j+1]보다 사전식 순서가 앞에 위치하는 것이고(순서가 더 빠르다)

name[j].compareTo(name[j+1]) = 0 이면, name[j]와 name[j+1]의 사전식 순서가 동일하다는 것이며

name[j].compareTo(name[j+1]) > 0 이면, name[j]가 name[j+1]보다 사전식 순서가 뒤에 위치하는 것(순서가 더 느리다)이다.

여기서는 쓰이지 않았지만, 두 문자열 str1, str2에 대해서 두 문자열이 동일한지, 동일하면 true(1), 동일하지 않다면 false(0)를 반환하는 메서드는 equals다. 즉, str1.equals(str2)와 같이 사용하면 된다.

 

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
package CH1;
 
import java.util.Scanner;
 
public class Code21 {
    static int n;
    static int[][] grid;
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        grid = new int[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                grid[i][j] = sc.nextInt();
            }
        }
        for (int x = 0; x < n; x++) {
            for (int y = 0; y < n; y++) {
                for (int dir = 0; dir < 8; dir++) {
                    for (int len = 1; len <= n; len++) {
                        int val = computeValue(x,y,dir,len);
                        if(val != -1 && isPrime(val)) {
                            System.out.println(val);
                        }
                    }
                }
            }
        }
        
    }
    public static int computeValue(int x, int y, int dir, int len) {
        int val = 0;
        for (int i = 0; i < len; i++) {
            int digit = getDigit(x,y,dir,i);
            if(digit == -1) {
                return -1;
            }
            val = val * 10 + digit;
        }
        return val;
    }
    
    public static int getDigit(int x, int y, int dir, int k) {
        int newX = x;
        int newY = y;
        switch(dir) {
            case 0: newY -= k; break;
            case 1: newX += k; newY -= k; break;
            case 2: newX += k; break;
            case 3: newX += k; newY += k; break;
            case 4: newY += k; break;
            case 5: newX -= k; newY += k; break;
            case 6: newX -= k; break;
            case 7: newX -= k; newY -= k; break;
        }
        if(newX < 0 || newX >= n || newY < 0 || newY >= n) {
            return -1;
        }
        return grid[newX][newY];
    }
    
    public static boolean isPrime(int val) {
        boolean b = true;
        if(val < 2) {
            b = false;
        }
        for (int i = 2; i*<= val ; i++) {
            if(val % i == 0) {
                b = false;
                break;
            }
        }
        return b;
    }
    
}
 
cs

 

val = val * 10 + digit 부분에 해당

i칸 떨어진 자리에 있는 digit를 반환하는 것이기 때문에, i는 i == 0(즉, 한자리 수)부터 i == (len-1)(즉, len자리 수)까지 증가할 수 있다.

 이때, x와 y의 양의 방향을 각각 오른쪽 방향, 아래쪽 방향으로 잡았다.

 

두번째 방법의 코드에서는 아래와 같이 좌표의 증감분에 대한 배열들을 미리 만들어 놓아서 첫번째 코드의 switch문을 대신할 수 있다.

 

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
package CH1;
 
import java.util.Scanner;
 
public class Code21 {
    static int n;
    static int[][] grid;
    static int[] offsetX = {0,1,1,1,0,-1,-1,-1};
    static int[] offsetY = {-1,-1,0,1,1,1,0,-1};
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        grid = new int[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                grid[i][j] = sc.nextInt();
            }
        }
        for (int x = 0; x < n; x++) {
            for (int y = 0; y < n; y++) {
                for (int dir = 0; dir < 8; dir++) {
                    for (int len = 1; len <= n; len++) {
                        int val = computeValue(x,y,dir,len);
                        if(val != -1 && isPrime(val)) {
                            System.out.println(val);
                        }
                    }
                }
            }
        }
        
    }
    public static int computeValue(int x, int y, int dir, int len) {
        int val = 0;
        for (int i = 0; i < len; i++) {
            int digit = getDigit(x,y,dir,i);
            if(digit == -1) {
                return -1;
            }
            val = val * 10 + digit;
        }
        return val;
    }
    
    public static int getDigit(int x, int y, int dir, int k) {
        int newX = x + k*offsetX[dir];
        int newY = y + k*offsetY[dir];
        
        if(newX < 0 || newX >= n || newY < 0 || newY >= n) {
            return -1;
        }
        else {
            return grid[newX][newY];
        }
    }
    
    public static boolean isPrime(int val) {
        boolean b = true;
        if(val < 2) {
            b = false;
        }
        for (int i = 2; i*<= val ; i++) {
            if(val % i == 0) {
                b = false;
                break;
            }
        }
        return b;
    }
    
}
 
cs

 

메서드 끝.

+ Recent posts