ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 6. 자바 오브젝트와 클래스 (2) 클래스
    자바(Java) 강의 2019. 3. 29. 13:56

    지난 포스트에서 자바가 기본적으로 제공하는 built-in 오브젝트에 대해 이야기해 보았다. 이번 포스트에서는 자바가 제공하는 오브젝트가 아닌 내가 만드는 오브젝트, 나의 코드를 담을 수 있는 오브젝트를 만들기 위해 무엇을 해야 하는지 알아보도록 하겠다.

    예상 독자

    목표

    • 클래스(Class)
    • 클래스의 멤버 변수
    • 클래스의 메서드
    • 클래스의 생성자 (Constructor)
    • 연습문제

    클래스(Class)

    오브젝트에는 그 오브젝트가 사용 할 수 있는 변수와 메서드가 있다고 했다. 예를 들어 배열 오브젝트는 length라는 변수를 마침표를 이용해 가져 올 수 있었고, String형의 오브젝트는 length()라는 메서드를 마침표를 통해 사용할 수 있었다. 즉 누군가(자바를 만드는 사람들)가 String이라는 형의 오브젝트는 length라는 메서드를 사용할 수 있도록 그 메서드의 코드 부분을 작성 해 놓았다는 뜻이다. 그리고 그 누군가가 String이라는 형의 오브젝트를 제공하기 위해 여러 가지 메서드를 작성해 준 것처럼, 우리도 우리만의 오브젝트가 사용 할 수 있는 변수와 메서드를 만들고, 그 오브젝트를 생성해 사용 할 수 있다. 이런 오브젝트를 생성하고 사용 하기 위헤 작성된 실제 코드 부분을 '클래스'라고 부른다. 클래스란 건축에서 설계도와 같은 것이다. 건축가는 설계도를 그린다. 실제로 건물을 만들기 위해서는 그 설계도를 보고 건물을 지어야 한다. 설계도를 만드는 과정이 클래스를 만드는 과정에 비유되고, 건물을 짓는 과정이 오브젝트를 생성하고 사용하는 것에 비유된다. 예를 들어 보자.

    src폴더(또는 main메서드가 있는 폴더)에 마우스 오른쪽 클릭을 한 후 New > Java Class를 누른다.

    그리고 이름에는 원하는 이름을 적는다. 나는 MyObject라는 오브젝트를 만들었다. OK를 누르면 다음과 같은 코드가 자동으로 생성된다.

    public class MyObject {
    
    }
    

    public - 나중에 설명할 테니 지금은 넘어가자.

    class - 이것은 클래스라는 뜻이다. class다음엔 클래스 이름을 적어주고 { } 안에 클래스의 내용을 적어준다.

    클래스의 내용이란 무엇인가? 바로 이 클래스가 오브젝트로 생성될 때, 그 오브젝트가 사용할 수 있는 변수와 메서드이다. 오브젝트라 0개 이상의 변수와 0개 이상의 메서드로 구성될 수 있는 이유는 그 오브젝트의 설계도인 클래스에 0개 이상의 변수와 0개 이상의 메서드가 작성되어 있기 때문이다.

    클래스의 멤버 변수

    코드 안에 아래처럼 변수를 만들어 보자.

    public class MyObject {
        int value;
    }
    

    위처럼 클래스 안에 바로 존재하는 변수를 그 클래스의 멤버 변수라고 부른다. 이제 메인 메서드로 돌아가, 이 클래스의 오브젝트를 만들어 보자.  

    public class Main {
        public static void main(String[] args) {
            MyObject myObject = new MyObject();
            myObject.value = 1;
    
            System.out.println(myObject.value);
        }
    }

    String오브젝트를 만들 때 어떻게 했었나? String str = new String("abc");이런 식으로 생성 해 주 었었다. 마찬가지로 우리가 방금 만든 MyObject클래스형의 변수 myObject를 선언(MyObject myObject)하고, 실제로 오브젝트를 생성(new MyObject();)하여 할당한다. 그러고 나서 myObject를 누르면 value라는 변수가 나올 것이다. 이 변수가 무엇인가? 바로 위에서 우리가 만든 int value 이 값이다. 여기다가 1을 넣고 System.out.println 해 보면 1이라는 결과가 나올 것이다.

    위에서 클래스라는 것은 설계도라고 했다. 설계도 하나만 있다면 똑같은 모양의 건축물을 여러 개 만들 수 있다. 따라서 MyObject라는 형의 오브젝트를 당연히 여러 개 만들 수 있다.

    public class Main {
        public static void main(String[] args) {
            MyObject myObject = new MyObject();
            myObject.value = 1;
    
            MyObject mySecondObject = new MyObject();
            mySecondObject.value = 100;
    
            System.out.println(myObject.value);
            System.out.println(mySecondObject.value);
        }
    }

    우리는 MyObject라는 클래스(설계도)를 가지고 myObject와 mySecondObject라는 서로 다른 두 개의 오브젝트를 만들었다. 두 오브젝트 생기기만 비슷하게 생겼을 뿐 서로 알지 못하는, 서로 완전히 다른 개체이다. 따라서 myObject.value에 1을 넣고, mySecondObject.value에 100을 넣고 출력하면 1과 100이 각각 나온다. 기술적으로 말하자면 new MyObject();를 할 때마다 해당 오브젝트를 저장하기 위한 메모리 공간이 하나씩 할당된다. 따라서 둘은 같은 설계도를 보고 만들었지만 다른 장소에 세워진 각각의 건축물이라고 생각하면 이해가 쉽다.

    그렇다면 한 클래스에 변수는 하나만 넣을 수 있는가? 아니다. 변수를 여러 개 넣을 수 있다. 이번에 String형의 변수인 name을 만들어 보자.

    public class MyObject {
        String name;
        int value;
    }
    

    여기서 알 수 있는 점은 무엇인가? 그렇다, String형은 오브젝트를 생성해야 사용할 수 있는 형이었다. 이를 통해 어떠 클래스는 기본 자료형(primitive data type)뿐만 아니라, 다른 클래스 형의 변수를 가질 수 있다는 것을 알 수 있다.

    이제 main으로 가서 name에 새 String 오브젝트를 할당하도록 하겠다.

    public class Main {
        public static void main(String[] args) {
            MyObject myObject = new MyObject();
            myObject.value = 1;
            myObject.name = new String("This is my first object");
    
            System.out.println(myObject.name);
            System.out.println(myObject.value);
        }
    }
    This is my first object
    1

    또 심지어는 자기 자신의 클래스형을 멤버 변수로 가지고 있을 수도 있다.

    public class MyObject {
        MyObject nextObject;
    }

    위의 멤버 변수 nextObject에는 MyObject형의 오브젝트가 들어갈 수 있다는 뜻이다.

    참고!

    위와 같이 현재 오브젝트에 다음 오브젝트(nextObject)가 연결되어 있는 자료구조를 보통 링크드 리스트(Linked List)라고 부른다.

    클래스의 메서드

    클래스에는 여러 가지 메서드를 작성할 수 있다. 예를 들어 다음과 같은 코드를 보자.

    public class MyObject {
        String name;
        int value;
    
        void print() {
            System.out.println(name + " : " + value);
        }
    }

    우리는 이 클래스에 리턴 타입이 void, 메서드 이름은 print, 그리고 매개변수는 받지 않는() 메서드를 작성하고, 그 내부에 System.out.println문을 작성하였다. 이 메서드 내부에서는 name과 value를 사용할 수 있다. 왜냐하면 name과 value는 클래스 내부 전체에게 공개된 것이기 때문이다. 이런 것을 scope이라고 부르는데 이에 대한 내용은 이후의 포스트에 대해 다루도록 할 테니 지금은 클래스 안의 메서드를 정의하고 사용하는 법에 집중해 보자. 위처럼 코드를 수정했으면 다시 메인으로 가, System.out.println대신 myObject.print를 해 보자.

    public class Main {
        public static void main(String[] args) {
            MyObject myObject = new MyObject();
            myObject.value = 1;
            myObject.name = new String("This is my first object");
            myObject.print();
        }
    }

    실행 결과는 다음과 같을 것이다.

    This is my first object : 1

    메서드 또한 여러 개 만들 수 있다. MyObject클래스에 add라는 메서드를 만들고 int를 매개변수로 받아보자. 메서드 내부에서는 현재 value에 매개변수로 넘어온 정수 값을 더하고, 위의 실행 결과와 같은 문장을 출력하도록 하자. 지금부터 5분간 스스로 해 보고 다음으로 넘어가도록 하라.

    public class MyObject {
        String name;
        int value;
    
        void print() {
            System.out.println(name + " : " + value);
        }
        
        void add(int num) {
            value = value + num;
            System.out.println(name + " : " + value);
        }
    }

    위와 같이 짰다면 훌륭하다. 하지만 여기서 더 개선할 수 있다. 중복되는 코드가 보이는가? System.out.println이 중복되는 것 같다. 현재 우리는 같은 클래스 안에 있으므로, name과 value가 같은 클래스 내부에 있기 때문에 메서드 안에서 사용할 수 있었던 것처럼 메서드도 같은 클래스 내에 있다면 마음대로 갖다 쓸 수 있다.

    public class MyObject {
        String name;
        int value;
    
        void print() {
            System.out.println(name + " : " + value);
        }
    
        void add(int num) {
            value = value + num;
            print();
        }
    }

    위처럼 print 메서드를 재사용하여 코드를 짤 수 있다. 이제 이 메서드를 메인에서 생성한 오브젝트를 이용해 불러보자.

    public class Main {
        public static void main(String[] args) {
            MyObject myObject = new MyObject();
            myObject.value = 1;
            myObject.name = new String("This is my first object");
            myObject.add(1000);
        }
    }

    실행 결과

    This is my first object : 1001

    add메서드가 실행된 것을 확인할 수 있다. 이번에는 value에 실제로 1001이 들어갔는지 확인해 봐라.

    클래스의 생성자 (Constructor)

    String오브젝트를 생성할 때 큰따옴표로 된 문자열을 값으로 넘겨줬던 것이 생각나는가?

    new String("This is my first object");

    new다음에는 클래스(오브젝트의 형) 이름을 적고 그다음에는 () 괄호를 적는다. 이 전개 어디서 많이 본 전개가 아닌가? 그렇다. 메서드 콜을 할 때 항상 메서드 이름(매개변수 1, 매개변수 2..) 이렇게 메서드 콜을 했었다. 메서드는 매개변수가 0개 일 수도, 여러 개 일 수도 있었다. 그렇다면 우리가 오브젝트를 만들 때 new 다음에 적는 것은 무엇인가? 바로 메서드 콜이다. 어떤 메서드 콜이냐 하면, 이 클래스의 오브젝트를 생성하는 메서드 콜이다. 이렇게 클래스의 오브젝트를 생성할 때 부르는 특별한 메서드를 '생성자'라고 부른다. 생성자에는 몇 가지 규칙이 있다. 그 규칙은 바로 모든 클래스는 적어도 하나의 생성자를 가지며, 리턴 타입을 명시하지 않고, 클래스 이름과 생성자의 이름은 같아야 한다는 것이다.

    1. 모든 클래스는 적어도 하나의 생성자를 가진다.

    2. 생성자는 리턴 값이 없고 리턴 타입을 명시하지 않는다.

    3. 생성자의 이름은 클래스의 이름과 같아야 한다.

    예를 통해 확인해 보자.

    public class MyObject {
        String name;
        int value;
        
        MyObject() {
            // 생성자
        }
    
        void print() {
            System.out.println(name + " : " + value);
        }
    
        void add(int num) {
            value = value + num;
            print();
        }
    }

    위처럼 다른 메서드들은 리턴 값이 없으면 void로 리턴 값이 없음을 명시하는 반면에 생성자 메서드인 MyObject(){}는 그런 것이 없다. 왜냐하면 모든 생성자의 리턴 타입은 자기가 생성한 오브젝트이기 때문이다. 또 생성자의 이름이 클래스의 이름과 같은 것을 확인할 수 있다. 그다음에는 메서드에서 했던 것처럼 똑같이 매개변수를 위한 괄호를 적고 {}를 이용해 바디 부분을 만들어준다. 

    이 타이밍에서 질문이 생겼길 바란다. 그 질문은, "1번에서 모든 클래스는 적어도 하나의 생성자를 가진다고 했는데, 위의 멤버 변수와 메서드를 실습할 때 생성자가 없었는데 왜 에러가 나지 않았을까?"이다. 그 이유는, 우리가 위처럼 생성자를 직접 만들어 주지 않으면, 자바가 알아서 매개변수를 받지 않는 생성자를 하나 만들어 주기 때문이다. 

    이번엔 매개변수를 받는 생성자를 만들어 보자.

    public class MyObject {
        String name;
        int value;
    
        MyObject(String n, int v) {
            name = n;
            value = v;
        }
    
        void print() {
            System.out.println(name + " : " + value);
        }
    
        void add(int num) {
            value = value + num;
            print();
        }
    }

    위에서 만든 생성자는 String형의 변수 하나와 int형의 변수를 하나 받아 각각 name과 value에 할당해 준다. 이렇게 하고 나면 main에서 에러가 날 것이다.

    무슨 뜻인지 짐작할 수 있겠는가? 메서드를 할 때도 위와 같은 에러를 만난 적이 있었다. 매개변수를 2개 받는 메서드(생성자)를 만들었는데 지금 아무 값도 넘겨주지 않고 있기 때문에 이런 에러가 발생하는 것이다. 이제 n과 v값을 차례로 넣어보자.

    public class Main {
        public static void main(String[] args) {
            MyObject myObject = new MyObject(new String("This is my first object"), 1);
            myObject.print();
        }
    }

    이 코드는 위에서 했던 print실습과 결과가 같을 것이다.

    This is my first object : 1

    생성자를 이용했더니 바뀐 점이 뭐가 있는가? 첫 번째로 name과 value값을 강제로 초기화해야 되었다는 것이다. 이렇게 어떤 오브젝트 안의 변수들이 반드시 초기화되어야 하는 경우 생성자를 이용해 값을 넘기게 하면 프로그래머로 하여금 반드시 초기화하게 할 수 있다는 장점이 있다. 두 번째로는 코드가 간결해진다는 점이다. 위에서 myObject.name = new String(); myObject.value = 1; 그리고 오브젝트 생성 부분까지 세줄에 써야 할 것을 1줄에 끝냈다. 적절하게 잘 사용한다면 생성자는 여러분의 코드를 간결하게 만들어 줄 것이다.

    연습하기

    우리집 고양이!! 

    실습을 위해 고양이라는 클래스를 만들어 보도록 하자. 클래스의 이름은 Cat이다. 이 고양이는 이름, 나이, 성별, 색깔이라는 속성을 가지고 있고, 먹다, 자다, 화장실을 이용한다, 창 밖을 본다는 기능을 한다. 기능의 경우 먹다, 자다, 화장실을 이용한다, 창 밖을 본다 등등은 여러분의 상상력을 이용하여 매개변수와 리턴 값을 정해보도록 해라. 예를 들어 다음과 같이 짤 수 있다.

    public class Cat {
        String name;
    
        void eat(int portion) {
            System.out.println(name + "(이)는 " + portion + " 만큼의 밥을 먹었다.");
        }
    }

    위를 토대로 다른 속성과 메서드를 추가하여 클래스를 완성시켜보도록 하자. 이 클래스를 발전시켜 나중에 고양이 키우기 같은 작은 다마고치 게임을 만들 수 있다. 하지만 지금 단계에서는 일단 기본적인 정보를 포함하는 클래스를 만들어 보는 연습을 하자. 메인이 존재하는 폴더에 클래스를 생성해야 함을 잊지 말자.

    참고

    사실 String형의 오브젝트는 매우 매우 많이 쓰이기 때문에 자바에서는 String형의 오브젝트 생성을 위해 new String("내용");을 매번 할 필요가 없다. 지금까지는 오브젝트에 대한 이해를 돕기 위해 new String();을 썼지만 큰따옴표 된 문자열을 바로 String형의 변수에 할당하면 자바가 알아서 String형의 오브젝트를 만들어 준다. 예를 들어,

    public class Cat {
        String name;
    
        void eat(int portion) {
            String behavior = name + "(이)는 " + portion + " 만큼의 밥을 먹었다.";
            System.out.println(behavior);
        }
    }

    behavior는  new String()으로 생성하지 않고 큰따옴표로 된 문자열을 바로 집어넣었다. 문자열에서 +는 전에도 말했지만 문자열을 이어 붙이는 기능을 한다. 

    아직도 헷갈리는 부분이 많을 것이다. public이란 무엇인가? name, value는 왜 메서드 안에서 사용 가능한가(scope)? 왜 굳이 main과 같은 폴더에 클래스를 생성해야 하는가? 

    지금 가지고 있는 의문들을 어딘가에 기록 해 놓길 바란다. 포스트가 계속 진행되면서 여러분이 가졌던 의문을 차차 풀어지길 바란다. 

    다음 포스트:7. 자바 패키지(Package)

    댓글

f.software engineer @ All Right Reserved