스터디할래 - 5주차 과제: 클래스

스터디할래 - https://github.com/whiteship/live-study

목표

 

자바의 Class에 대해 학습하세요.

 

 

학습할 것

 

  • 클래스 정의하는 방법

 

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

 

  • 메소드 정의하는 방법

 

  • 생성자 정의하는 방법

 

  • this 키워드 이해하기

 


 

클래스 정의하는 방법

 

만들고자 하는 객체를 구상하였다면 객체의 대표 이름을 하나 결정하고 그 이름을 클래스의 이름으로 한다. 예를 들어

 

동물 객체의 클래스는 Animal로, 상점 객체의 클래스는 Shop으로 이름을 줄 수 있다. 클래스 명은 다른 클래스와 식별이

 

가능해야 하므로 식별자 작성 규칙에 따라서 만들어야 한다.

 

 

번호 작성 규칙 예시
1 하나 이상의 문자로 이루어져야 한다. Shop, Animal, Item
2 첫번째 글자로 숫자는 불가능하다. 3Person(x) , 13Avenue(x)
3 '$', '_' 외의 특수 문자는 불가능하다. $Money, _Money, @Money(x), %Money(x)
4 자바의 키워드는 사용할 수 없다. int(x), while(x), continue(x)

 

 

관례적으로 클래스 이름이 단일 단어라면 첫 글자를 대문자로 하고 나머지 글자를 소문자로 작성한다. 만약 두 개 이상의

 

단어가 혼합되어 있다면 각각의 단어의 첫글자를 대문자로 작성한다.

 

 

클래스 이름 예시

public class Book {}

public class AdminController {}

public class ItemRepository {}

 

 

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

 

클래스를 정의하고 컴파일 과정을 마치면 해당 객체를 생성할 설계도가 만들어진 것과 같다. new 키워드를 사용하여 객

 

체를 생성하는 방법은 다음과 같다.

 

new 클래스 이름();

 

new는 클래스로부터 객체를 생성하는 연산자로, new 뒤에는 생성자(Constructor)가 온다. 생성자는 "클래스 이름()"의

 

형식이며, new로 생성된 객체는 메모리 힙(Heap) 영역에 생성이 된다. new 연산자는 객체를 생성하고, 객체의 주소를

 

반환(Return)한다. 이 주소를 참조 타입(Reference Type)인 클래스 변수에 저장하고 변수를 통해 객체를 사용할 수 있다.

 

위의 설명을 코드로 나타내면 아래와 같다.

 

 

클래스이름 변수명;
변수 = new 클래스 이름();

ex)

Car car;
car = new Car();


한 줄로 합치는 것도 가능하다.

Car car = new Car();

 

  

 

메소드 정의하는 방법

 

클래스에서 메소드는 객체의 동작에 해당하는 이름을 가진 중괄호 블록을 말한다. 메소드를 호출하면 중괄호 블록에 있

 

는 모든 코드들이 일괄적으로 실행된다. 메소드 내에서 다른 객체를 생성하거나, 객체 간의 데이터를 전달하는 목적으로

 

사용한다. 파라미터(Parameter)를 받을 수도, 안 받을 수도 있으며 이것은 메소드 정의에 따라 달라진다.

 

메소드 정의 예시는 다음과 같다.

 

 

public class Person {
	
    private int age;
    private String name;
    
    // 생성자 관련 코드...
    
    
    public void run() {
    // 달리기 method	
    
    // TODO: 달리는 동작에 대한 구현 코드
    }
    
    public int getMyAge() {
    	return this.age;
    }
}


public class Main {

	public static void main (String[] args) {
    	
        // Person 객체를 생성
        Person person = new Person();
        
        // 정의한 메소드를 사용.
        person.run();
        
        // 메소드를 호출하여 반환 값을 전달 받아 사용.
        int personAge = person.getMyAge();
        
        
    }
}

 

 

생성자 정의하는 방법

 

생성자(Constructor)는 new 연산자와 같이 사용된다. 클래스로부터 객체를 생성할 때 호출되어 객체의 초기화를 담당한

 

다. 객체의 초기화란 필드를 초기화하거나, 메소드를 호출해서 객체를 사용할 준비를 세팅하는 것이라 볼 수 있다. new

 

연산자에 의해 생성자가 성공적으로 실행되면 Heap 영역에 객체가 생성되고 객체의 주소를 반환한다. 

 

 

 

기본 생성자(Default Constructor)

 

 

모든 클래스는 하나 이상의 생성자를 가지는데, 생성자의 선언을 생략하였다면 컴파일러가 기본 생성자를 바이트 코드

 

에 자동으로 추가한다. 

 

// 생성자의 형태
[public] 클래스 이름() {}

// 예시
public class Song {

	public Song() {} // 자동으로 추가 되며, 생략이 가능하다.
}

 

주의할 점이 있는데 만약, 명시적으로 선언한 생성자가 존재 할 경우 컴파일러는 기본 생성자를 추가하지 않는다. 따라

 

서 명시적으로 선언한 생성자가 존재할 때 기본 생성자가 존재하지 않는 오류가 발생할 수 있으니 이 때는 기본 생성자

 

를 추가해줘야 한다.

 

 

생성자를 명시적으로 선언하는 방법은 다음과 같으며 선언 방법 및 실제 코드 동작은 아래 코드 구현에서 설명한다.

 

클래스 이름(매개변수) {
   // 객체 초기화 관련 코드
}

예시

public class Person {
    private int myAge;

    public Person(int age) {
        myAge = age;
    }
	
    // toString()을 재정의 하여  생성된 Person 객체의 프로퍼티 값을 출력
    @Override
    public String toString() {
        return "Person{" +
                "myAge=" + myAge +
                '}';
    }
}

public class Main {
    public static void main(String[] args) {
        Person person = new Person(1); // 생성자의 인자로 1을 넘겨 주는 코드
        System.out.println(person.toString());
    }
}


출력 결과:
Person{myAge=1}

 

 

this 키워드 이해하기

 

this 키워드는 객체 자신의 참조를 나타내며, 우리가 우리 자신을 "나"라고 하듯 객체가 객체 자신을 나타내기 위해 this

 

를 사용한다.  사용 예시를 보자.

 

public class Person {
    private int age;
    private String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }
    public Person(int age) {
        this.age = age;
        this.name = "홍길동";
    }
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

public class Main {
    public static void main(String[] args) {
        Person person1 = new Person(10, "토르");
        Person person2 = new Person((20));

        System.out.println("person1 = " + person1);
        System.out.println("person2 = " + person2);
    }
}

출력 결과:
person1 = Person{age=10, name='토르'}
person2 = Person{age=20, name='홍길동'}

 

코드 설명:

 

age와 name을 인자로 받는 생성자는 각각 전달받는 값을 Person 객체의 age와 name으로 설정하지만

 

age만을 받는 생성자는 age는 전달받은 값으로 하고, this(Person 객체의). name은 "홍길동"으로 생성된다.  따라서

 

person1의 age와 name은 각각 전달받은 값이 되어 객체가 생성되고, person2의 age와 name은 age만 전달받고, name

 

은 홍길동이 되어 있는 것을 출력 결과를 통해 할 수 있다. 

 

 

과제

  • int 값을 가지고 있는 이진 트리를 나타내는 Node 라는 클래스를 정의하세요.

 

  • int value, Node left, right를 가지고 있어야 합니다.

 

  • BinrayTree라는 클래스를 정의하고 주어진 노드를 기준으로 출력하는 bfs(Node node)와 dfs(Node node) 메소드를 구현하세요.

 

  • DFS는 왼쪽, 루트, 오른쪽 순으로 순회하세요.

 

 

Node.java

package me.study.studyhalle.week5;

import lombok.*;

@NoArgsConstructor
@Getter @Setter
public class Node {
    private int value;
    private Node left;
    private Node right;

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

 

 

BinaryTree.java

package me.study.studyhalle.week5;


import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.LinkedList;
import java.util.Queue;

@NoArgsConstructor
@Getter @Setter
public class BinaryTree {
    private Node root;
    private Queue<Node> queue;
    //순회 횟수를 세기 위한 변수
    private int bfsCount;
    private int dfsCount;

    public BinaryTree(Node root) {
        this.root = root;
        queue = new LinkedList<>();
        this.bfsCount = 0;
        this.dfsCount = 0;
    }

    public void bfs(Node node) {
        if (node == null) {
            return;
        }
        if (queue.isEmpty()) {
            queue.add(node);
        }
        while (!queue.isEmpty()) {
            Node peek = queue.peek();
            this.bfsCount++;
            System.out.print(peek.getValue() + " ");
            if (peek.getLeft() != null) {
                queue.add(peek.getLeft());
            }
            if (peek.getRight() != null) {
                queue.add(peek.getRight());
            }

            queue.poll();
            bfs(queue.peek());
        }
    }

    public void dfs(Node node) {
        if (node == null) {
            return;
        }
        dfsCount++;
        dfs(node.getLeft());
        System.out.print(node.getValue() + " ");
        dfs(node.getRight());
    }
}

 

 

 

 

 

BinaryTreeTest.java

package me.study.studyhalle.week5;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;


class BinaryTreeTest {
    BinaryTree binaryTree;
    Node node1;
    Node node2;
    Node node3;
    Node node4;
    Node node5;
    Node node6;
    Node node7;
    Node node8;
    Node node9;
    Node node10;
    Node node11;
    Node node12;
    Node node13;
    Node node14;
    Node node15;
    Node node16;

    @BeforeEach
    void setUp() {
        node1 = new Node(1);
        node2 = new Node(2);
        node3 = new Node(3);
        node4 = new Node(4);
        node5 = new Node(5);
        node6 = new Node(6);
        node7 = new Node(7);
        node8 = new Node(8);
        node9 = new Node(9);
        node10 = new Node(10);
        node11 = new Node(11);
        node12 = new Node(12);
        node13 = new Node(13);
        node14 = new Node(14);
        node15 = new Node(15);
        node16 = new Node(16);

        binaryTree = new BinaryTree(node1);

        Node root = binaryTree.getRoot();
        root.setLeft(node2);
        root.setRight(node3);
        node2.setLeft(node4);
        node2.setRight(node5);
        node3.setLeft(node6);
        node3.setRight(node7);
        node4.setLeft(node8);
        node4.setRight(node9);
        node5.setLeft(node10);
        node5.setRight(node11);
        node6.setLeft(node12);
        node6.setRight(node13);
        node7.setLeft(node14);
        node7.setRight(node15);
        node8.setLeft(node16);
    }
    @AfterEach
    void afterEach() {
        binaryTree.setBfsCount(0);
        binaryTree.setDfsCount(0);
    }

    @DisplayName("bfs 방식으로 순회 테스트 - 루트부터 시작해서 모든 노드 탐색 - 순회한 노드의 총 갯수는 16이다")
    @Test
    void bfs() {
        Node root = binaryTree.getRoot();
        binaryTree.bfs(root);
        assertThat(binaryTree.getBfsCount()).isEqualTo(16);
    }

    @DisplayName("bfs 방식으로 순회 테스트 - 2부터 시작해서 모든 노드 탐색 - 순회한 노드의 총 갯수는 8이다")
    @Test
    void bfs2() {
        binaryTree.bfs(node2);
        assertThat(binaryTree.getBfsCount()).isEqualTo(8);
    }

    @DisplayName("bfs 방식으로 순회 테스트 - 16부터 시작해서 모든 노드 탐색 순회한 노드의 총 갯수는 1이다(자기 자신 뿐)")
    @Test
    void bfs3() {
        binaryTree.bfs(node16);
        assertThat(binaryTree.getBfsCount()).isEqualTo(1);
    }
    @DisplayName("bfs 방식으로 순회 테스트 - 노드가 null 일 경우 순회 횟수는 0이다")
    @Test
    void bfs4() {
        binaryTree.bfs(null);
        assertThat(binaryTree.getBfsCount()).isEqualTo(0);
    }

    @DisplayName("dfs 방식으로 순회 테스트 - 모든 노드 탐색 - 순회한 노드의 총 갯수는 16이다")
    @Test
    void dfs() {
        Node root = binaryTree.getRoot();
        binaryTree.dfs(root);
        assertThat(binaryTree.getDfsCount()).isEqualTo(16);
    }

    @DisplayName("dfs 방식으로 순회 테스트 - 2부터 시작해서 중위 순회 탐색 - 순회한 노드의 총 갯수는 8이다")
    @Test
    void dfs2() {
        binaryTree.dfs(node2);
        assertThat(binaryTree.getDfsCount()).isEqualTo(8);
    }

    @DisplayName("dfs 방식으로 순회 테스트 - 16부터 시작해서 중위 순회 탐색 - 순회한 노드의 총 갯수는 1이다(자기 자신 뿐)")
    @Test
    void dfs3() {
        binaryTree.dfs(node16);
        assertThat(binaryTree.getDfsCount()).isEqualTo(1);
    }

    @DisplayName("dfs 방식으로 순회 테스트 - 노드가 null 일 경우 순회 횟수는 0이다")
    @Test
    void dfs4() {
        binaryTree.dfs(null);
        assertThat(binaryTree.getDfsCount()).isEqualTo(0);
    }

}

 

테스트 결과

 

 

 

References

이것이 자바다(신용권 저, 한빛미디어)