순간을 성실히, 화려함보단 꾸준함을

5주차 과제: 클래스 본문

백기선님과 함께 하는 자바 스터디

5주차 과제: 클래스

폭발토끼 2021. 6. 6. 14:20

목표

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

학습할 것(필수)

  1. 클래스 정의하는 법
  2. 객체 만드는 방법(new 키워드 이해하기)
  3. 메서드 정의하는 방법
  4. 생성자 정의하는 방법
  5. this 키워드 이해하기
  6. (번외)Nested Class

과제(Optional)

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

클래스 정의하는 법

클래스란??
=- 변수(데이터,속성)과 메서드(행위)를 가지고 있는 집합을 뜻합니다.
class 키워드를 사용하여 선언하고 ,new 연산자를 통해 인스턴스를 할당합니다.

규칙

  • 첫번째 글자는 숫자가 올 수 없습니다.
  • '$,'_' 이외의 특수 문자는 사용할 수 없습니다.
  • 자바 키워드는 사용할 수 없습니다.
  • 하나 이상의 문자로 이루어져야 합니다.
public class 클래스이름{
}

*주의할점 : 파일이름과 동일한 이름의 클래스 선언에서만 public 접근 제한자를 붙일 수 있습니다.
따라서 한 파일 내 다수의 클래스를 선언할땐 파일이름과 같지 않은 클래스는 public 접근 제한자를 붙일 수 없습니다.

Person.java FIle

public class Person{
}
class Man{
}
컴파일시 Person.class Man.class 파일이 생성됩니다.

클래스는 크게 3가지의 구성요소를 가지고 있습니다.

  1. 필드(Field) : 데이터를 담을 수 있는 곳
  2. 메서드(Method) : 객체의 동작을 정의할 수 있는 곳
  3. 생성자(Constructor) : 객체를 생성할 때 초기화를 담당하는 곳
public class Person{
    //필드
    int age;

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

    //메서드
    void get_age(){...}
}
  1. 필드
    - 객체가 가지고 있는 데이터를 의미
    - 객체가 소멸되기 전까지 사용할 수 있습니다.
  2. 메서드
    - 객체의 동작을 정의할 수 있습니다.
    - 메서드는 선언부(자료형,메서드 이름, 매개변수) 와 실행 블록으로 이루어져 있습니다.
  3. 생성자
    - 반드시 클래스 내부에 존재해야 하며, 다수의 생성자를 가질 수 있습니다.
    - new 연산자를 이용하여 인스턴스를 생성할 수 있고, 이때 자동으로 디폴트 생성자가 호출이 됩니다.
    - 메서드와 비슷한 형태를 가지고 있지만 리턴타입이 정의되어 있지 않습니다.
    - 만약 생성자를 따로 정의하지 않는다면 컴파일러는 자동으로 디폴트 생성자를 바이트코드에 추가시킵니다.

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

new 키워드란?
- 인스턴스(객체)를 생성할 때마다 사용되는 키워드 이며 Java에서는 객체 변수가 실제 데이터가 아닌 데이터를 담고 있는 공간의 주소값(참조값)을 가지고 있다는 특징이 존재합니다.

new 는 클래스 타입의 인스턴스(객체)를 생성해 주는 역할을 수행합니다.
즉, new 키워드를 사용하여 인스턴스(객체)를 생성하면 Heap 영역에 데이터를 저장할 공간을 할당받은 후 그 공간에는 데이터를 담고 있는 주소값(reference value)를 객체에게 반환해 줍니다.
이후 생성자를 호출합니다.
$$클래스\ 객체\ 변수 = new\ 클래스();$$

public class Person{
    int age;
    public static void main(String[] args){
        Person person = new Person(); //Person 객체 생성
    }
}

메서드 정의하는 방법

메서드란?
- 특정 기능을 정의한 코드들의 집합을 뜻합니다. 선언부(자료형,메서드 이름,매개변수)와 실행 블록으로 이루어져 있습니다.

String regPerson(String name){
    ...
    return retName;
}

메서드는 오버로딩(Overloading)과 오버라이딩(Overriding)이 가능합니다.
오버로딩이란?
- 두개 이상의 메서드가 같은 이름을 가지고 있으나 매겨변수의 수가 다르거나 자료형이 다른 경우를 뜻합니다.
오버라이딩이란?
- 상위클래스에서 정의되어져 있는 메서드와 동일한 메서드가 하위클래스에 재정의 하는 것을 의미합니다. 오버라이딩은 상속과 연관이 깊습니다.

생성자 정의하는 방법

생성자란?
- 객체를 생성할때 호출되는 것으로, 객체를 초기화 해주기 위해 처음으로 실행되는 메서드이다.
- 유일한 반환타입이 존재하지 않는 메서드이다.
- 반드시 클래스 이름과 같아야 합니다.

class Piter{
    int age;
    //생성자
    Piter(){
        age=25;
    }
}
public class Person{
    public static void main(String[] args){
        Piter piter = new Piter();
        System.out.println(piter.age); //25 출력
    }
}

보시다시피 객체를 생성하면 자동으로 생성자를 호출하게된다.
만약 생성자를 따로 정의를 해주지 않더라도 컴파일러는 자동으로 생성자를 생성한다. 이를 디폴트 생성자라고 한다.

정리를 다시 해보면 생성자는 크게 3가지로 나눌 수 있다.

  1. 소스 o / 매개변수 o : 매개변수가 있는 생성자
  2. 소스 o / 매개변수 x : 매개변수가 없는 생성자(No-args 생성자)
  3. 소스 x / 매개변수 x : 소스로 정의가 되어있지도 않은 생성자(디폴트 생성자)

중요한 점은 디폴트 생성자는 No-args 생성자 이지만 결코 No-args 생성자는 디폴트 생성자가 아니라는 점이다.

//No-args 생성자
class Piter{
    int age;
    //생성자
    Piter(){
        age=25;
    }
}
//디폴트 생성자
class Piter{
    int age;
}

또한, 한가지 더 알아야 할 점은 상속과도 연관성이 깊은 내용이다. 자식클래스로 인스턴스(객체)를 생성할때 부모클래스의 생성자가 자동으로 호출된다는 점이다.
따라서 부모클래스의 생성자를 선언해 줄때 No-args 생성자를 생성해 주지 않으면 에러가 발생하는 것을 확인할 수 있다.

class Piter_Father{
    int age;
    Piter_Father(int age){...} //에러가 발생
}
class Piter extends Piter_Father{
    int age;
    Piter(){
        age=25;
    }
}
public class Person{
    public static void main(String[] args){
        Piter piter = new Piter();
    }
}

먼저 Person 클래스의 main 함수에서 자식클래스인 Piter로 객체를 생성해 주었다. 그러면 당연하게 Piter의 생성자가 호출이 될것이다. 그러나 이 소스는 에러가 발생한다. 이유는 부모클래스의 생성자를 먼저 호출하기 때문이다.
현재 저 소스에는 No-args 생성자가 선언이 되어있지 않기 때문에 결국 에러가 발생하는 것이다.

class Piter_Father{
    int age;
    Piter_Father(){...} //No-args 생성자 추가
    Piter_Father(int age){...}
}
class Piter extends Piter_Father{
    int age;
    Piter(){
        age=25;
    }
}
public class Person{
    public static void main(String[] args){
        Piter piter = new Piter();
    }
}

이를 No-args 생성자를 매번 추가해 주며 해결할 수 있지만 이런 방법은 매우 비효율적이다.따라서 우린 super라는 키워드를 사용하여 이런 문제를 해결 할 수 있다.

class Piter_Father{
    int age;
    Piter_Father(int age){...}
}
class Piter extends Piter_Father{
    int age;
    Piter(){
        super(55); //super 추가
        age=25;
    }
}
public class Person{
    public static void main(String[] args){
        Piter piter = new Piter();
    }
}

super키워드는 부모를 의미하고 괄호() 값에 파라미터를 추가하여 부모클래스의 생성자를 호출한다. 이로써 발생했던 문제점을 해결할 수 있게되었다.
주의할 점super키워드는 반드시 자식생성자의 첫줄에 위치해야 된다는 점이다. 또한, 부모클래스의 생성자의 매개변수와 자료형을 동일시 해주어야 한다.

this 키워드 이해하기

this란?
- 객체,자기 자신을 나타내는 명령어를 뜻합니다.

크게 3가지 형태로 사용됩니다.

1) 매개변수와 클래스의 필드값의 이름이 동일할때

public class Person{
    int age;
    Person(int age){
        this.age = age;
    }
}

만약 this키워드를 사용하지 않고 초기화를 해주려고 했다면 초기화가 정상적으로 적용이 되지 않습니다. 이유는 this키워드를 사용하지 않는다면 매개변수 = 파라미터라는 식을 가지게 됩니다.

2) 오버로딩 된 다른 생성자를 호출 하고 싶을때

public class Person{
    int x,y;
    Person(){
        this(10,20);
    }
    Person(int x,int y){
        this.x = x;
        this.y = y;
    }
}
//x=10,y=20 이 할당됨

주의할 점this는 반드시 생성자의 첫번째줄에 위치해야 한다는 점 입니다.

3) 자신의 참조값을 리턴시키고 싶을때

public class Person{
    int x;
    Person(int x){
        this.x=x;
    }
    Person retPerson(){
        return this;
    }
}

+코드블록과 생성자 초기화 방법

맴버변수를 초기화 할 수 있는 방법은 2가지가 있습니다.

  • 초기화 블록
  • 생성자 초기화
public class Init {
    int x;
    static int  y;

    //코드블록 초기화 방법
    static
    {
        y=10;
    }
    {
        x=5;
    }
    public static void main(String[] args) {
        Init init = new Init();
        System.out.println("x : " + init.x);
        System.out.println("y : " + init.y);
    }
}

이때 static 블록은 클래스가 로딩시 단 한번만 실행이 된다. 이를 유의하자

그럼 만약 초기화 블록생성자 초기화를 둘다 동시에 사용하게 된다면 뭐가 우선순위가 높을까?

public class Init {
    int x;
    {
        x=5;
    }
    Init(){
        x=10;
    }
    public static void main(String[] args) {
        Init init = new Init();
        System.out.println("x : " + init.x); //10이 출력된다.
    }
}

생성자가 초기화블록보다 높은 우선순위를 갖는다. 단, 결코 초기화 블록이 실행이 되지 않는게 아닙니다. 오버로딩 되는 것 입니다.

Nested Class 출처

Nested Class란?

- Class안에 또 다른 Class를 추가할 수 있습니다. 이를 Nested Class라고 합니다. 크게 2가지로 나뉘는데 static nested classinner class 로 나뉩니다.

Nested Class를 사용하는 방법은
바깥클래스 이름.내부 클래스 이름 = new 바깥클래스이름.내부 클래스 이름()
로 인스턴스를 생성할 수 있습니다.

public class OuterClass {
    static class StaticNestedClass {...}
    class InnerClass {...}
}

중첩 클래스는 이 중첩 클래스를 둘러 쌓고 있는 바깥 클래스의 맴버변수 입니다.
따라서 바깥 클래스의 필드에 얼마든지 접근이 가능합니다.
그러나 static nested class는 필드에 접근을 할 수 없습니다.

static nested class

static nested class는 바깥 클래스의 필드값에 직접적으로 접근 할 수 없습니다.
따라서 그 클래스의 객체 참조를 사용할 수 밖에 없습니다.

public class OuterClass{
    public static class StaticNestedClass{...}
}
public class Main{
    public static void main(Strin[] args){
        OuterClass.StaticNestedClass staticNestedClass = new OuterClass.StaticNestedClass();
    }
}

inner class

inner class는 객체의 필드와 메서드에 바로 접근이 가능합니다.
그러나 바깥클래스의 맴버변수로 취급받으므로 static키워드는 사용 불가합니다. 다만 final키워드를 사용하면 가능합니다.

public class OuterClass {
    public class InnerClass {
        static int static_number = 10; // 컴파일 에러 발생
        static final int static_final_number = 10; // 선언 가능
    }
}

static키워드를 사용하면 안될까요?
그 이유는 바깥클래스에 대해 종속성을 가지고 있기 때문입니다. 바깥클래스의 인스턴스를 다수 생성했다고 하면, static_number에 접근하려고 할때 어느 인스턴스의 static_number값이 옳은 것 인지 알 수가 없기 때문입니다.
그러나 final키워드를 사용하게 된다면 컴파일 시점에 결정이 되버리고 변경을 할 수 없는 상수가 되버리기 때문에 사용할 수 있게 됩니다.

public class OuterClass {
    public class InnerClass {
    }
}

public class Main {
    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();        
        OuterClass.InnerClass innerClass = outerClass.new InnerClass();
    }
}

{바깥 클래스의 이름}.this.{멤버 변수}를 사용해서 바깥클래스의 맴버변수에 접근할 수 있습니다.

public class OuterClass {
    private int number = 10;

    public class InnerClass {
        private int number = 20;

        public void method(int number) {
            System.out.println(number); // 5 이 출력됩니다.
            System.out.println(this.number); // 20 이 출력됩니다.
            System.out.println(OuterClass.this.number);  // 10 이 출력됩니다.
        }
    }
}
public class Main {
    public static void main(String[] args) {
        OuterClass outerClass = new OuterClass();
        OuterClass.InnerClass innerClass = outerClass.new InnerClass();
        innerClass.method(5);
    }
}

과제

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

다음 포스팅에 내용을 담겠습니다.