[5주차] 클래스

janjanee·2022년 8월 1일
0
post-thumbnail

2021.01.07 작성글 이전

5. 클래스

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

클래스와 객체의 관계는 제품 설계도와 제품과의 관계로 비유할 수 있다.

TV 설계도(클래스)
TV (객체)

TV 설계도를 가지고 TV를 생산할 수 있다. TV 설계도가 TV인가? 그렇지 않다. 따라서, 클래스가 객체 그 자체는 아니다. 클래스는 객체를 만들기 위해 필요할 뿐이다.

다음 표는 TV 설계도 외에도 클래스와 객체간의 관계를 예로 보여준다.

클래스객체
스마트폰 설계도아이폰, 갤럭시
TV 설계도LG TV, 삼성 TV
붕어빵 기계팥 붕어빵, 슈크림 붕어빵

붕어빵 기계로 내가 좋아하는 슈크림 붕어빵과 그냥저냥인 팥 붕어빵을 만들었다. 이 과정을 클래스의 인스턴스화 라고 하며 만들어진 슈크림 붕어빵을 인스턴스 라고 한다.

객체와 인스턴스는 같은 의미이지만, 객체는 모든 인스턴스를 대표하는 포괄적인 의미를 갖고있으며
인스턴스는 어떤 클래스로부터 만들어진 것인지를 강조하는 보다 구체적인 의미를 갖고있다.

5-1. 클래스 정의하는 방법

클래스 정의하는 방법을 알아보자.

클래스를 만들려면 클래스 이름이 필요하다. 관례적으로 클래스 이름은 대문자로 시작하며 나머지는 소문자로 작성한다.

Person // (o)
2Person  //  (x)  처음에 숫자가 올 수 없다.
@person! //  (x)  '$', '_' 외의 특수 문자는 사용 불가능
접근제어자 class 클래스명 {

}

public class Person {

}

Person.java 라는 클래스 파일을 생성하고 위와 같이 작성할 수 있다.

public class Person {
    // 필드
    int age;
    String name;

    // 생성자
    Person () {...}

    // 메소드
    void eat() {...}
}

클래스의 구성 멤버로는 필드, 생성자, 메소드가 존재한다.

필드

객체 고유의 상태 정보를 저장한다. 객체가 소멸되지 않는 한 객체와 함께 존재한다. 위의 Person 클래스를 보면 사람은 이름과 나이가 고유의 상태로 존재하므로 필드에 선언한다.

생성자

new 연산자로 호출되는 특별한 {} 괄호 블록이다. 객체 생성 시 초기화를 담당한다. 필드를 초기화하거나, 메소드를 호출해서 객체 사용 준비를한다. 메소드와 비슷하지만, 클래스 이름으로 되어 있고 리턴 타입이 없다.

생성자가 성공적으로 실행되면 힙(heap) 영역에 객체가 생성되고 객체의 주소가 리턴된다.

메소드

객체의 동작에 해당하는 {} 블록이다. 메소드는 이름을 가지고 있다. 메소드를 호출하면 괄호 블록에 있는 모든 코드들이 일괄적으로 실행된다.

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

클래스명 변수명;    // 클래스의 객체를 참조하기 위한 참조변수 선언
변수명 = new 클래스명();    // 클래스의 객체를 생성 후, 객체의 주소를 참조변수에 저장

Tv t;   // Tv 클래스 타입의 참조변수 t 선언
t = new Tv();   // TV 인스턴스를 생성 후, 생성된 Tv 인스턴스의 주소를 t에 저장

Tv t = new Tv();    // 한 줄로 줄여서 사용

그림으로 살펴보면 다음과 같다.

  1. Tv t; Tv 클래스 타입의 참조변수 t를 선언. 메모리에 참조변수 t의 공간이 생성된다. 아직 인스턴스가 생성되지 않았으므로 참조변수는 비어있다.
  2. t = new Tv(); 연산자 new에 의해 Tv 클래스의 인스턴스가 메모리의 빈 공간에 생성된다. 주소가 0x100인곳에 생성되었다고 가정하자. 이 때, 멤버변수는 각 자료형에 해당하는 기본값으로 초기화된다. 그 다음, 대입연산자에 의해서 생성된 객체의 주소값이 참조변수 t에 저장된다.

5-3. 메소드 정의하는 방법

  • 메소드를 사용하는 이유
  • 높은 재사용성
  • 중복된 코드 제거
  • 프로그램의 구조화

메소드의 선언과 구현

메소드는 크게 선언부(header, 머리)구현부(body, 몸통)로 이루어져 있다.

선언부

반환타입(출력) 메소드이름 매개변수선언(입력)

int add (int x, int y) { ... }
  • 매개변수 선언 x, y 두 개의 정수를 입력받아서, 두 값을 더한 결과(int 타입)을 반환한다. 값을 입력받을 필요가 없다면 () 빈괄호를 작성한다.

    매개변수도 메서드 내에 선언된 것이므로 '지역변수'이다.

  • 메소드 이름 변수의 명명규칙대로 작성. 주로 동사인 경우가 많다 (특정 작업을 수행하므로), 이름만으로도 메서드의 기능을 쉽게 알 수 있도록 함축적이면서 의미있는 이름을 짓도록 신중해야함.

  • 반환타입 메소드의 작업 수행 결과 반환할 타입을 적는다. 반환 값이 없을 경우 'void'로 작성

구현부

선언부 다음에 오는 괄호 {}, 메소드를 호출했을 때 수행될 문장들을 작성.
  • return문 반환타입이 'void'가 아닌 경우에 구현부 안에 반드시 return 반환값;이 포함되어야 한다. 반환타입과 일치하거나 자동 형변환이 가능한 타입이어야 한다.
  • 지역변수(local variable) 메소드 내에 선언된 변수들은 그 메서드 내에서만 사용가능하다. 즉, 서로 다른 메소드라면 같은 이름의 변수를 선언해도 상관없다.

메소드 호출

메서드를 정의 하고 호출하지 않으면 아무런 일이 일어나지 않는다. 메소드를 호출해야만 구현부 {} 안의 문장들이 수행된다.

메소드이름(1,2, ...);

// 1
print();

// 2
int result = add(10, 20);

인자(argument)와 매개변수(parameter)

메소드를 호출 할 때 괄호 () 안에 지정한 값들을 인자(argument)라고 한다. 인자의 개수와 순서는 호출된 메소드에 선언된 매개변수(parameter)와 일치해야 한다.

return 문

현재 실행중인 메소드를 종료하고 호출한 메소드로 되돌아간다.

모든 메소드에는 적어도 하나의 return문이 있어야 한다. 반환값이 void인 경우에는 컴파일러가 메서드의 마지막에 return을 자동적으로 추가해준다.

void printHello () {
    System.out.println("hello");
    return;     // 생략가능. 컴파일러가 자동 추가
}

하지만, void가 아니라면 반드시 return문을 작성해주어야 한다.

클래스 메소드(static메소드)와 인스턴스 메소드

  • 인스턴스 메소드는 인스턴스 변수와 관련된 작업을 하는, 즉 메소드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메서드이다.
  • 인스턴스와 관계없는 (인스턴스 변수나 인스턴스 메소드를 사용하지 않는) 메소드를 클래스 메소드(static 메소드)로 정의한다.

인스턴스 변수를 사용하지 않는다고 해서 반드시 클래스 메소드로 정의해야하는 것은 아니지만 특별한 이유가 없는 한 그렇게 하는 것이 일반적이다.

  1. 클래스 설계 시, 멤버변수 중 모든 인스턴스에서 공통으로 사용하는 것에 static 추가
  2. 클래스 변수(static 변수)는 인스턴스를 생성하지 않아도 사용 가능
  3. 클래스 메소드(static 메소드)는 인스턴스 변수를 사용할 수 없음
  4. 메소드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려

오버로딩(overloading)

한 클래스 내에 같은 이름의 메소드를 여러 개 정의

  1. 메소드 이름이 같아야 한다.
  2. 매개변수의 개수 또는 타입이 달라야 한다.

반환 타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.

void println()
void println(boolean x)
void println(char x)
void println(int x)
...

println 메소드의 오버로딩 예시이다.

가변인자(varargs)와 오버로딩

가변인자 : JDK 1.5부터 매개변수 동적으로 지정 가능

String concatenate(String... str) { ... }

concatenate();
concatenate("a");
concatenate("a", "b");
concatenate(new String[]{"A", "B"});
  • 가변인자를 선언한 메소드를 오버로딩하면, 메소드를 호출했을 때 구별이 되지 못하는 경우가 발생하기 때문에 주의

5-4. 생성자 정의하는 방법

인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메소드'이다.

생성자의 조건은 다음과 같다.

  1. 생성자의 이름은 클래스의 이름과 같아야 한다.
  2. 생성자는 리턴 값이 없다.
class Card {

  Card() {  // 매개변수가 없는 생성자
    ...   
  }

  Card(String k, int num) {   // 매개변수가 있는 생성자
    ...       
  }
}

기본 생성자 (default constructor)

모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다. 그러나 클래스에 생성자를 정의하지 않으면 컴파일러가 제공하는 '기본 생성자(default constructor)' 때문이다.

컴파일 할 때, 생성자가 하나도 없다면 컴파일러는 아래와 같이 기본 생성자를 추가한다.

클래스 이름() {}

Card() {}

과연 정말 컴파일러가 기본 생성자를 만들까? 한 번 확인해보자

Test.java

class Test {
  int num;
}

멤버변수 num 하나를 갖고 있는 클래스를 생성했다. 어떠한 생성자도 정의하지 않은 상태이다.

javac Test.java
javap -c Test

컴파일 후 Test.class의 내용을 열어보자.

Compiled from "Test.java"
class Test {
  int num;

  Test();     // 여기있다! default 생성자!
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V

컴파일러가 default생성자를 만들어주는 것을 확인가능하다.

❗️ 기본 생성자가 컴파일러에 의해서 추가되는 경우는 클래스에 정의된 생성자가 하나도 없을 때 뿐이다.

만약, 사용자가 정의한 생성자가 하나라도 존재한다면 디폴트 생성자는 자동으로 생성되지 않는다. 이런경우 디폴트 생성자가 필요하면 사용자가 직접 작성해야한다.

매개변수가 있는 생성자

class Car {
  String color;
  String gearType;
  int door;

  Car() {...}

  Car(String c, String g, int d) {
    color = c;
    gearType = g;
    door = d;
  }
}

위의 Car 클래스에는 default 생성자와 매개변수가 있는 생성자가 정의되어있다.

자동차 인스턴스를 생성하면서 색상, 변속기종류, 문 개수를 지정하고 싶다면 각각의 생성자는 아래와 같다.

// 1. default 생성자

Car c = new Car();
c.color = "white";
c.gearType = "auto";
c.door = 4;


// 2. 매개변수 있는 생성자
Car c = new Car("white", "auto", 4);

위 두개의 코드는 같은 내용이지만, 2번의 매개변수 있는 생성자가 더 간결하고 직관적이다.

5-5. this 키워드 이해하기

this : 인스턴스 자신을 가리키는 참조변수, 인스턴스의 주소가 저장되어있다. 모든 인스턴스 메소드에 지역변수로 숨겨진 채로 존재 this(), this(매개변수) : 생성자. 같은 클래스의 다른 생성자를 호출할 때 사용

this(), this(매개변수)

Car(String color) {
  door = 5;
  Car(collor, "auto", 4);   // Error.
}

위의 코드는 에러가 발생하는데

  1. 생성자의 두 번째 줄에서 다른 생성자 호출
  2. this(color, "auto", 4); 로 호출해야함

위 두가지 조건에 만족하지 못했기 때문이다.

this

아래는 위에서 정의한 매개변수 있는 생성자인데 만약 매개변수로 선언된 변수의 이름이 인스턴스 변수와 같을 때에도 this를 사용한다.

Car(String color, String gearType, int door) {
  this.color = color;
  this.gearType = gearType;
  this.door = door;
}

이유는 color = color; 라고 하면 두 값이 구분되지 않으므로, this.color = color;라고 작성하면 this 키워드로 인해 인스턴스 변수의 color 라고 알 수 있기 때문이다.

당연한 말이지만, static메소드(클래스 메소드)에서는 인스턴스 멤버들을 사용할 수 없는것처럼 this 역시 사용할 수 없다.

5-6. 과제 (옵션)

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

트리

트리 과제를 하기전에 우선 트리(tree) 구조에 대해 전체적으로 살펴보자.

트리를 구성하는 요소는 노드(node)와 가지(edge)이다. 각각의 노드는 가지를 통해 다른 노드와 연결되어있다. 그림에서 색칠된 부분은 서브트리 라고 부른다.

루트

트리의 가장 윗부분에 위치하는 노드를 루트(root)라고 한다. 하나의 트리에는 하나의 루트가 있다.

리프

트리의 가장 아랫부분에 위치하는 노드를 리프(leaf)라고 한다. 가장 아랫부분이란말이 물리적으로 가장 아랫부분에 위치하는게 아니라, 더 이상 뻗어나갈 수 없는 마지막에 노드가 위치한다는 의미이다.

다른 용어로 끝 노드(terminal node) 또는 바깥 노드(external node)

안쪽 노드

루트를 포함하여 리프를 제외한 노드를 안쪽 노드라고 한다.

다른 용어로 끝이 아닌 노드 (non-terminal node)

자식

어떤 노드로부터 가지로 연결된 아래쪽 노드랄 자식(child)이라고 한다. 예를 들어, 그림의 X는 2개, Y는 3개의 자식을 가지고 있다.

부모

노드에서 가지로 연결된 위쪽 노드를 부모라고한다. 예를 들어 Y의 부모는 X 이다.

레벨

루트로부터 얼마나 떨어져 있는지에 대한 값을 레벨 이라고 한다. 루트의 레벨은 0이고, 루트로 부터 하나씩 아래로 뻗어나갈 때마다 레벨이 1씩 증가한다.

차수

노드가 갖는 자식의 수를 차수(degree)라고 한다. 예를들어, X의 차수는 2 Y의 차수는 3이다.

모든 노드의 차수가 n 이하인 트리를 n진 트리라고한다.

모든 노드의 자식 수가 2개 이하인 경우가 이진트리이다.

널 트리

노드, 가지가 없는 트리를 널 트리(null tree)라고 한다.

순서 트리와 무순서 트리

형제 노드의 순서가 있는지 없는지에 따라 트리를 두 종류로 분류합니다.
형제 노드의 순서가 있다면, 순서 트리(ordered tree) 순서가 없다면 무순서 트리(unordered tree) 라고합니다.

순서 트리 탐색

순서 트리의 노드를 탐색 하는 두 가지 경우가 있는데 이진트리를 예로 설명합니다.

  • 너비 우선 탐색(breadth-first Search)

    낮은 레벨에서 시작해 왼쪽 -> 오른쪽 방향으로 검색하고 한 레벨에서의 검색이 끝나면

    다음레벨로 내려갑니다.

  • 깊이 우선 탐색(depth-first Search)
    • 리프까지 내려가면서 검색하는 것을 우선순위로 하는 탐색 방법
    • 리프에 도달해 더 이상 검색을 진행할 곳이 없는 경우 부모에게 돌아간다.
    • 그 다음 다시 자식 노드로 내려간다.

깊이 우선 탐색의 경우 언제 노드를 방문할지는 다음과 같이 세 종류로 구분한다.

  • 전위 순회(Preorder)

    노드 방문 -> 왼쪽 자식 -> 오른쪽 자식

A -> B -> D -> H -> E -> I -> J -> C -> F -> K -> L -> G

  • 중외 순회(Inorder)

    왼쪽 자식 -> 노드 방문 -> 오른쪽 자식

    H -> D -> B -> I -> E -> J -> A -> K -> F -> L -> C -> G

  • 후위 순회(Postorder)

    왼쪽 자식 -> 오른쪽 자식 -> (돌아와) 노드 방문

    H -> D -> I -> J -> E -> B -> K -> L -> F -> G -> C -> A

이진트리

노드가 왼쪽 자식과 오른쪽 자식을 갖는 트리를 이진트리(binary tree)라고 한다.

노드의 자식은 2개 이하만 유지해야 한다.

완전이진트리

  1. 마지막 레벨을 제외한 레벨은 노드를 가득 채운다.
  2. 마지막 레벨은 왼쪽부터 오른쪽 방향으로 노드를 채우되 반드시 끝까지 채울 필요는 없다.

이진검색트리

이진검색트리(binary search tree)는 이진트리가 다음 조건을 만족하면 된다.

  1. 어떤 노드 N을 기준으로 왼쪽 서브 트리 노드의 모든 키 값은 노드 N보다 작아야 한다.
  2. 오른쪽 서브 트리 노드의 키 값은 노드 N보다 커야 한다.
  3. 같은 키 값을 갖는 노드는 없다.

위의 내용을 기반으로 과제인 Node 클래스와 BinaryTree 클래스를 생성한 결과이다.

참고로 Test 코드의 bfs와 dfs의 출력 결과를 그림으로 나타내보면 다음과 같다. 강조된 노드가 출발 기준노드이다.

References

  • 남궁성, 『자바의 정석』, 도우출판(2016)
  • 신용권, 『이것이 자바다』, 한빛미디어(2018)
  • Bohyoh Shibata, 『자료구조와 함께 배우는 알고리즘 입문』, 이지스퍼블리싱(2018)
  • 출처가 없는 이미지는 직접 그림
profile
얍얍 개발 펀치

0개의 댓글