Notice
Recent Posts
Recent Comments
Link
«   2025/12   »
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
Tags
more
Archives
Today
Total
관리 메뉴

기록하자..

클래스 | 백기선님 LIVE-STUDY 본문

자바

클래스 | 백기선님 LIVE-STUDY

P23Yong 2022. 5. 2. 09:44

클래스

학습할 것

  • 클래스 정의하는 방법
  • 객체 만드는 방법 (new 키워드 이해하기)
  • 메소드 정의하는 방법
  • 생성자 정의하는 방법
  • this 키워드 이해하기

클래스 정의하는 방법

클래스 작성 규칙

  1. 하나 이상의 문자로 이루어져야 한다.
    • Hello
  2. 첫 번째 글자는 숫자가 올 수 없다.
    • 2Hello (X)
  3. '$', '_' 외의 특수문자는 사용할 수 없다.
    • $Hello, _Hello
    • @Hello (X)
  4. 자바 키워드는 사용할 수 없다.
    • int (X), for (X)

클래스 작성 규칙은 관례적으로 첫 글자를 대문자로, 나머지는 소문자로 작성한다. 만약 서로 다른 단어가 혼합된 경우라면 각 단어의 첫 글자는 대문자로 작성한다.

이제 클래스 작성 규칙을 알고 클래스의 이름을 알았다면 다음과 같이 소스 파일을 생성한다.

'클래스이름'.java

이후 클래스를 선언해준다.

public class Hello {

}

일반적으로 소스 파일 하나당 클래스를 하나 선언하게 된다. 하지만, 두 개 이상의 클래스를 선언해도 된다.

public class Hello {

}

class Hello2 {

}

다음 Hello.java를 컴파일해보면 Hello.class와 Hello2.class 파일이 생성되는 것을 확인할 수 있다.

주의할 점은 파일 이름과 동일한 이름의 클래스 선언에만 public 접근 제한자를 붙일 수 있다는 것이다. Hello2에 public을 붙여 컴파일하게 되면 컴파일 에러가 나게된다.

class Hello2 is public, should be declared in a file named Hello2.java

클래스의 구성

클래스 안에는 객체가 가져야 할 구성 멤버가 선언된다. 구성 멤버에는 필드, 생성자, 메서드가 있다.
다음과 같은 클래스를 살펴보자.

class Car {

    // 필드
    private int tireNum;

    // 생성자
    public Car() {}

    public Car(int tireNum) {
        // ...
    }

    // 메서드
    public void setTireNum(int tireNum) {
        // ...
    }
}
  • 필드
    • 필드는 객체의 데이터가 저장되는 곳이다.
    • 필드의 선언은 클래스의 중괄호 {} 내부 어디서든 가능하다. 하지만 생성자나 메서드 내부에 선언하게 되면 그것들은 모두 로컬 변수가 된다.
    • 선언 형태는 변수와 비슷하다. 필드는 클래스의 인스턴스, 객체가 소멸되지 않는 한 객체와 함께 존재한다. 하지만 변수는 생성자나 메서드 내에서만 사용되고 생성자나 메서드가 종료되면 자동 소멸된다.
  • 생성자
    • 생성자는 클래스가 new 키워드에 의해 호출되는 특별한 중괄호 {} 블럭이다.
    • 보통 인스턴스를 초기화할 때 사용되고 생성자가 존재하지 않는다면 기본 생성자가 생성된다.
    • 만약 어떠한 생성자라도 존재한다면 기본 생성자는 생성되지 않는다.
    • 생성자는 클래스와 이름이 같아야 하고 리턴타입이 없다.
  • 메서드
    • 메서드는 객체의 동작을 표현하는 중괄호 {} 블럭이다.
    • 메서드를 호출하게 되면 중괄호 블럭에 있는 코드들이 일괄적으로 실행된다.

객체 만드는 방법 (new 키워드 이해하기)

클래스로부터 객체를 만드는 방법은 new연산자를 이용한다.
new는 클래스로부터 객체를 생성시키는 연산자이다.

클래스로부터 객체를 만드는 과정은 크게 세 가지 과정을 거친다.

  1. 선언 (Declaration)
  2. 인스턴스화 (Instantiation)
  3. 초기화 (Initialization)

선언

첫 번째로, 변수의 클래스 타입을 정의하지만 개체를 정의하지는 않는다. 다음은 일반적인 선언 구문이다.

Car myCar;

위의 변수는 아무것도 참조하지 않는 변수이다. myCar라는 변수는 Car라는 타입을 저장할 공간과 이에 대한 참조 값을 저장할 공간을 할당하게 된다.

인스턴스화

new연산자를 통해 객체의 복사본을 가져와 변수에 할당해주어야 한다.

new연산자는 새로운 객체에 대한 메모리를 할당하해 클래스를 인스턴스화하고 메모리에 대한 참조를 반환하는 연산자다. 이 참조가 변수에 저장되는 것이다.

그래서 자바에서 모든 클래스의 객체는 동적으로 할당된다.

초기화

new연산자는 또한 새로운 객체를 초기화시킬 클래스의 생성자를 호출한다. 생성자는 클래스의 객체가 생성되었을 때 무엇을 할지 정의한다.

Car myCar = new Car();

위의 경우는 클래스의 기본 생성자를 호출하게 된다.

생성자에서 코드가 실행되면 객체가 초기화되고 초기화된 객체의 참조가 반환된다.

메서드 정의하는 방법

메서드란 객체의 동작에 해당하는 중괄호 {} 블럭을 말한다. 중괄호 블럭의 이름은 메서드 이름이고, 메서드를 호출하게 되면 블럭에 있는 코드들이 모두 실행된다.

메서드 선언

메서드 선언은 선언부(리턴타입, 메서드 이름, 매개변수선언)와 실행부로 나뉜다. 메서드 선언부를 메서드 시그니처라고도 한다.

리턴타입 메서드이름 (매개변수 선언, ...) {
    // 실행할 코드를 작성
}
int solve(int number, double dNumber) {
    // 코드 작성
}
  • 리턴타입
    • 메서드가 실행 후 리턴하는 값의 타입을 말한다.
    • 메서드는 리턴값이 있을 수도 있고, 없을 수도 있다. 없는 경우 void를 사용한다.
  • 메서드 이름
    • 자바 식별자 규칙에 맞게 작성하면 되지만, 다음 사항에 주의하자.
      • 숫자로 시작하면 안되고, $와 _를 제외한 특수문자를 사용하지 않는다.
      • 관례적으로 메서드명은 소문자로 시작한다.
      • 서로 다른 단어가 혼합된 이름이면 뒤이어 오는 단어의 첫머리 글자는 대문자이다.
  • 매개 변수 선언
    • 매개 변수는 메서드가 코드 블럭을 실행할 때 필요한 데이터를 외부로부터 받기 위해 사용된다.
  • 메서드 바디
    • 중괄호로 감싼 영역이다.

메서드 오버로딩

자바는 메서드 오버로딩을 지원하는데, 오버로딩이란 클래스 내에 같은 이름의 메서드를 여러 개 선언하는 것을 말한다. 메서드 오버로딩의 조건은 매개 변수의 타입, 개수, 순서 중 하나가 달라야 한다.

public class DataArtist {
    // ...
    public void draw(String s) {
        // ...
    }

    public void draw(int i) {
        // ...
    }

    public void draw(double d) {
        // ...
    }

    public void draw(int i, float f) {
        // ...
    }
}
draw("something");
draw(100);

위의 두 draw메서드들이 실행하는 코드들은 다르다.

메서드를 오버로딩할 때 주의할 점은 매개 변수의 타입과 개수, 순서가 똑같을 경우 매개 변수 이름만 바꾸는 것은 메서드 오버로딩이라 할 수 없다. 또한 리턴 타입을 통해서 다름을 구분하지 않는다. 따라서 다른 리턴 타입을 가진다하더라도 메서드 시그니처가 같다면 정의할 수 없다.

참고

오라클 튜토리얼-메서드 정의

생성자 정의하는 방법

생성자는 new연산자와 같이 사용되어 클래스로부터 객체를 생성할 때 호출되어 객체의 초기화를 담당한다. 생성자를 실행시키지 않고는 클래스로부터 객체를 만들 수 없다. 생성자에 의해 객체가 힙 영역에 생성되고 객체의 주소가 리턴되면 그 주소를 가지고 객체에 접근할 떄 이용할 수 있다.

생성자 선언

생성자의 선언은 다음과 같은 형태로 작성된다.

클래스( 매개변수선언, ...) {
    // 객체의 초기화 코드
}
Car(String model, String color, int maxSpeed) {
    this.model = model;
    this.color = color;
    this.maxSpeed = maxSpeed;
}

위와 같이 Car의 생성자를 작성하면 new연산자를 통해 다음과 같이 객체를 생성할 수 있다.

Car myCar = new Car("그랜저", "검정", 300);

클래스에 위와 같이 생성자가 명시적으로 선언되어 있을 경우 반드시 선언된 생성자를 호출해서 객체를 생성해야 한다. 물론 생성자를 선언하지 않았다면 컴파일러는 중괄호 {} 블록내용이 비어 있는 기본 생성자를 바이트코드에 자동으로 추가시켜준다.

외부에서 제공하는 다양한 데이터를 받아 객체를 초기화하기 위해 다양한 생성자를 필요로 하는 경우도 있다. 그래서 자바는 생성자 오버로딩을 제공하고 있다. 생성자 오버로딩이란 생성자의 매개 변수를 달리하는 생성자를 여러개 선언하는 것을 말한다.

this 키워드 이해하기

this키워드는 인스턴스 메서드나 생성자 내에서 현재 객체를 의미한다. 우리는 this키워드를 통해 모든 인스턴스 멤버에 접근할 수 있다.

여기서 인스턴스 멤버란 객체를 생성한 후 사용할 수 있는 필드와 메서드를 말하는데, 이들을 각각 인스턴스 필드, 인스턴스 메서드라 부른다.
Point클래스를 예로 들어보자.

public class Point {
    public int x = 0;
    public int y = 0;

    // Constructor
    public Point(int a, int b) {
        x = a;
        y = b;
    }
}

이와 같이 작성했던 Point클래스를 this를 이용해 다음과 같이 작성할 수 있다.

public class Point {
    public int x = 0;
    public int y = 0;

    // Constructor
    public Point(int x, int y) {
        this.x = x
        this.y = y;
    }
}

this는 주로 생성자와 메서드의 매서드의 매개 변수 이름이 동일한 경우, 인스턴스 멤버인 필드임을 명시하고자 할 때 사용한다.

생성자 오버로딩시 다른 생성자 호출

this는 자기 자신의 인스턴스를 가리킨다고 위에서 배웠다. 생성자 오버로딩시 다른 생성자를 호출해야 한다면 this키워드를 이용해 호출할 수 있다. 이를 명시적 생성자 호출이라고 한다.

public class Point {
    public int x;
    public int y;
    public int z;
    public int time;

    public Point(int x, int y) {
        this(x, y, 0, 0);
    }

    public Point(int x, int y, int z) {
        this(x, y, z, 0);
    }

    public Point(int x, int y, int z, int time) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.time = time;
    }
}

위의 코드를 보면 생성자 내부에서 다른 생성자를 this를 통해 호출하고 있다. 이 경우 해당 생성자에서 객체를 생성하는 것이 아닌 this로 호출한 생성자로 위임하는 것이라고 이해하면 된다.

중요한 규칙으로는 this생성자는 반드시 생성자의 첫 번째 줄에 나와야 한다.

과제

  • int 값을 가지고 있는 이진 트리를 나타내는 Node 라는 클래스를 정의하세요.
  • int value, Node left, right를 가지고 있어야 합니다.
  • BinrayTree라는 클래스를 정의하고 주어진 노드를 기준으로 출력하는 bfs(Node node)와 dfs(Node node) 메소드를 구현하세요.
  • DFS는 왼쪽, 루트, 오른쪽 순으로 순회하세요.

Node 클래스

public class Node {
    private int value;
    private Node left;
    private Node right;

    public Node(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public Node getLeft() {
        return left;
    }

    public Node getRight() {
        return right;
    }

    public void setLeft(Node left) {
        this.left = left;
    }

    public void setRight(Node right) {
        this.right = right;
    }
}

BinaryTree 클래스

public class BinaryTree {

    private Node root;

    public void insertNode(Node node) {
        if (root == null) {
            root = node;
        } else {
            Node parentNode = root;
            Node curNode = root;

            int dir = 0;    // 0 -> left, 1 -> right

            while (curNode != null) {
                parentNode = curNode;
                if (curNode.getValue() < node.getValue()) {
                    curNode = curNode.getRight();
                    dir = 1;
                } else {
                    curNode = curNode.getLeft();
                    dir = 0;
                }
            }
            if (dir == 0) {
                parentNode.setLeft(node);
            } else {
                parentNode.setRight(node);
            }
        }
    }

    public void bfs(Node node) {
        Queue<Node> queue = new LinkedList<>();
        queue.add(node);

        while (!queue.isEmpty()) {
            Node cur = queue.poll();
            System.out.print(cur.getValue() + " -> ");

            if (cur.getLeft() != null) {
                queue.add(cur.getLeft());
            }
            if (cur.getRight() != null) {
                queue.add(cur.getRight());
            }
        }
    }

    public void dfsInorder(Node node) {
        if (node == null) {
            return;
        }

        dfsInorder(node.getLeft());
        System.out.print(node.getValue() + " -> ");
        dfsInorder(node.getRight());
    }

    public void dfsPreorder(Node node) {
        if (node == null) {
            return;
        }

        System.out.print(node.getValue() + " -> ");
        dfsPreorder(node.getLeft());
        dfsPreorder(node.getRight());
    }

    public void dfsPostorder(Node node) {
        if (node == null) {
            return;
        }

        System.out.print(node.getValue() + " -> ");
        dfsPostorder(node.getRight());
        dfsPostorder(node.getLeft());
    }
}

binaryTree는 이진 탐색 트리를 이용해 노드를 삽입하고 루트노드부터 순회하도록 하였다.

dfs는 전위, 중위, 후위 각각의 순회 방법을 함수로 만들어 두었다.

다음은 BinaryTree 클래스의 테스트 코드이다.

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class BinaryTreeTest {

    BinaryTree binaryTree;
    Node root;

    @BeforeAll
    void makeTree() {
        binaryTree = new BinaryTree();

        root = new Node(4);

        binaryTree.insertNode(root);
        binaryTree.insertNode(new Node(3));
        binaryTree.insertNode(new Node(5));
        binaryTree.insertNode(new Node(10));
        binaryTree.insertNode(new Node(1));
        binaryTree.insertNode(new Node(2));
        binaryTree.insertNode(new Node(7));
        binaryTree.insertNode(new Node(14));
    }


    @Nested
    @DisplayName("이진 탐색 트리에서의")
    class BinarySearchTree {

        @DisplayName("DFS 중위순회 탐색의 결과는 다음과 같습니다.")
        @Test
        void dfsInorderTest() {
            System.out.print("DFS (Inorder) = ");
            binaryTree.dfsInorder(root);
            System.out.println(" END");
        }

        @DisplayName("DFS 전위순회 탐색의 결과는 다음과 같습니다.")
        @Test
        void dfsPreorderTest() {
            System.out.print("DFS (Preorder) = ");
            binaryTree.dfsPreorder(root);
            System.out.println(" END");
        }

        @DisplayName("DFS 후위순회 탐색의 결과는 다음과 같습니다.")
        @Test
        void dfsPostorderTest() {
            System.out.print("DFS (Postorder) = ");
            binaryTree.dfsPostorder(root);
            System.out.println(" END");
        }

        @DisplayName("BFS 탐색의 결과는 다음과 같습니다.")
        @Test
        void bfsTest() {
            System.out.print("BFS = ");
            binaryTree.bfs(root);
            System.out.println(" END");
        }
    }
}
DFS (Postorder) = 4 -> 5 -> 10 -> 14 -> 7 -> 3 -> 1 -> 2 ->  END
BFS = 4 -> 3 -> 5 -> 1 -> 10 -> 2 -> 7 -> 14 ->  END
DFS (Inorder) = 1 -> 2 -> 3 -> 4 -> 5 -> 7 -> 10 -> 14 ->  END
DFS (Preorder) = 4 -> 3 -> 1 -> 2 -> 5 -> 10 -> 7 -> 14 ->  END

참고자료

이것이 자바다

new 연산자-geeksforgeeks

오라클 튜토리얼-메서드 정의

오라클 튜토리얼-this