자바(6) - 클래스

클래스와 객체 2

this

this란 자신의 메모리를 가리키는 예약어이다. 즉, 생성된 인스턴스 스스로를 가르킨다. 이를 활용하여 인스턴스의 멤버 변수라던가 메서드를 호출하여 이것저것 해볼 수 있다. 아래 예제는 this로 다른 생성자 메서드를 호출해보는 것이다.

class Person {
    String name;
    int age;
    
    Person() {
        this("이름 없음", 1);
    }
    
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class CallAnotherConst {
    public static void main(String[] args) {
        Person noName = new Person();
        System.out.println(noName.name);
        System.out.println(noName.age);
    }
}

우리가 main메서드에서 매개변수가 없는 인스턴스를 생성했다. 위의 코드를 보면 Person()이 실행될텐데, this(“이름 없음”, 1)이 보일 것이다. 이것을 통해 새로운 생성자를 호출하여 멤버 변수들에 값을 추가했다. this로 다른 생성자를 호출할 때 주의할 점으로는 this 이전에 다른 코드를 넣으면 안된다. 아래처럼 하면 IDE에서부터 빨간줄로 에러 표시를 해줄 것이다. 왜냐하면 생성자는 클래스가 생성될 때 호출되므로 클래스 생성이 완료되지 않은 시점에 다른 코드가 있으면 오류가 발생할 수 있기 때문이다.

Person() {
    this.name = "noname";
    this("이름 없음", 1);
}

this를 사용하여 생성된 클래스 자신의 주소 값을 반환할 수 있다. 인스턴스 주소 값을 반환할 때는 this를 사용하고 반환형은 클래스 자료형을 사용한다.

class Person {
    String name;
    int age;
    
    Person() {
        this("이름 없음", 1);
    }
    
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    Person returnItSelf() { // 자기 자신의 자료형 입력
        return this;
    }
}

public class CallAnotherConst {
    public static void main(String[] args) {
        Person noName = new Person();
        System.out.println(noName.name);
        System.out.println(noName.age);
        
        Person p = noName.returnItSelf();
        System.out.println(p);
        System.out.println(noName);
    }
}

returnItSelf 메서드는 this를 반환하기에 인스턴스 변수 p도 같은 인스턴스를 가르키고 같은 결과가 출력될 것이다.

객체 간 협력

객체 지향 프로그램은 객체를 정의하고 객체 간 협력으로 만드는 것이다. 예를 들어 학생 객체가 있고, 지하철 객체, 버스 객체를 만들어서 학생이 버스를 타면 버스에 맞는 협력이 지하철을 타면 지하철에 맞는 결과가 만들어 지는 것이다. 아래의 예제를 만들어 보자.

학생 클래스

public class Student {
    public String studentName;
    public int grade;
    public int money;
    
    public Student(String studentName, int money) {
        this.studentName = studentName;
        this.money = money;
    }
    
    public void takeBus(Bus bus) {
        bus.take(1000);
        this.money -= 1000;
    }
    
    
    public void takeSubway(Subway subway) {
        subway.take(1000);
        this.money -= 1500;
    }
    
    public void showInfo() {
        System.out.println(studentName + "님의 남은 돈은 " + money + "입니다.");
    }
}

takeBus, Subway 메소드를 보면 매개변수로 Bus와 Subway 자료형을 넣어 주었다. 이는 Bus와 Subway 참조변수로 지정하여 메소드들을 호출하기 위해서이다.

Bus 클래스

Subway클래스도 이름만 바꿔서 똑같이 해주면 된다.

public class Bus {
    int busNumber;
    int passengerCount;
    int money;
    
    public Bus(int busNumber) {
        this.busNumber = busNumber;
    }
    
    public void take(int money) {
        this.money += money;
        passengerCount++;
    }
    
    public void showInfo() {
        System.out.println("버스 " + busNumber + "번의 승객은 " + passengerCount + "명이고, 수입은 " + money + "입니다.");
    }
}

이제 클래스를 만들었으니 협력해보는 코드를 봐보자.

Student st1 = new Studnet("Hong", 5000);

Bus bus100 = new Bus(100);
st1.takeBus(bus100);
st1.showInfo();
bus100.showInfo();

static 변수

학생 클래수에서 학번을 우리가 지정하지 않고 자동으로 생성되게끔 하고 싶다. 그럴려면 생성될 때마다 순서대로 번호를 지정해주어야 하고, 인스턴스 멤버 변수가 아닌 클래스 전반적으로 공통으로 사용하는 변수를 만들 수 있어야 한다. 이를 위해 static 변수를 선언하여 사용한다. static 변수란 다른 용어로 ‘정적 변수’라고 하고 다음과 같이 선언한다.

static int serialNum;

기존 변수 선언에다가 앞에 static만 붙혀주면 된다. static 변수는 다른 멤버변수들 처럼 인스턴스가 생성될 때마다 새로 생성되는 변수가 아닌 프로그램이 실행되어 메모리에 올라갔을 때 딱 한 번 메모리 공간에 할당된다. 그리고 이 값을 모든 인스턴스가 공유하여 사용한다. static으로 선언한 변수는 인스턴스 생성과 상관없이 먼저 생성되고 그 값을 공유한다고 하여 클래스 변수라고도 부른다.

public class Student {
    public static int serialNum = 1000;
    public int studentID;
    
    public Student() {
        serailNum++;
        studentID = serialNum;
    }
}

static 메서드

static 변수가 있다면 static 메서드라는 것도 있다. static 메서드는 인스턴스 생성이 없이 사용할 수 있다. 선언은 다음과 같다. 리턴타입 앞에 static을 붙혀주면 된다.

public static int getSerialNum() {
}

클래스 메서드 내부에서는 인스턴스 변수를 사용할 수 없다. 아래처럼 사용하면 getSerialNum메서드의 두번째 라인에서 에러가 뜰 것이다. 왜냐하면 멤버변수는 인스턴스가 생성될때 할당되는 것인기에 static 메서드에서는 접근할 수 없기 때문이다.

public class Student2 {
    private static int serialNum = 1000;
    String studentName;
    
    public static int getSerialNum() {
        int i = 10;
        studentName = "홍석주";
        return serialNum;
    }
}

변수 유효 범위

세 가지의 변수 타입을 알아봤다.

  1. 함수나 메서드 안에서만 사용할 수 있는 지역 변수(로컬 변수)
  2. 클래스 안에서 사용하는 멤버 변수(인스턴스 변수)
  3. 여러 인스턴스에서 공통으로 사용할 수 있는 static 변수(클래스 변수)

위 변수들은 어디에 어떻게 선언되었는지 에 따라 유효 범위가 달라진다.

지역 변수의 유효 범위

지역 변수는 함수나 메서드 내부에 선언하기에 함수 밖에서 사용할 수 없다. 즉 어떤 함수에 선언된 변수들은 다른 함수들에서 접근이 불가하다. 지역 변수가 생성되는 메모리를 스택이라고 하는데 함수가 호출되면 스택에 쌓였다가 반환되면 메모리 공간이 해제되어 같이 없어진다.

멤버 변수의 유효 범위

멤버 변수는 인스턴스 변수라고도 한다. 클래스가 생성될 때 힙(heap)메모리에 생성된다. 멤버 변수는 클래스의 어느 메서드에서나 사용할 수 있고, 나중에 사용안하면 가비지 컬렉터에 의해 수거되고 사라진다. 따라서 클래스 내부의 여러 메서드에서 사용할 변수는 멤버 변수로 선언하는 것이 좋다.

static 변수의 유효 범위

사용자가 프로그램을 실행하면 메모리에 프로그램이 상주하는데, 이때 프로그램 영역 중에 데이터 영역이 있다. 이 영역에 상수와 문자열과 함께 static 변수가 생성된다. 인스턴스 변수는 객체가 생성되는 문자 new가 되어야 생성되지만, static 변수는 클래스 생성과 상관없이 데이터 영역에 생성된다. 이렇게 생성된 static 변수는 private이 아니라면 클래스 외부에서도 객체 생성과 무관하게 사용할 수 있다. 이후 프로그램 실행이 끝난 뒤 메모리에 내려가면(엑셀 닫기 같은거) static 변수도 소멸한다. static 변수는 프로그램이 시작할 때부터 끝날 때까지 메모리에 상주하므로 크기가 너무 큰 변수를 static으로 선언하는 것은 좋지 않다.

static 응용 - 싱글톤 패턴

프로그램을 구현하다 보면 여러 개의 인스턴스가 필요할 때가 있고, 하나의 인스턴스로만 관리할 때가 있다. 후자처럼 하나의 인스턴스만 생성하는 디자인 패턴을 싱글톤 패턴(singleton pattern)이라고 한다. 이 패턴은 프레임워크에서 많이 사용하는 패턴이라고 한다. 예를들면 회사와 직원들을 객체지향적으로 구현한다면, 직원들은 여러 명이니 여러 인스턴스로 나뉘겠지만 회사는 하나이니 회사를 싱글톤 패턴으로 만드는 것이다. 회사(Compnay)클래스를 기준으로 한 번 테스트 해보자.

프레임워크란 프로그램을 쉽게 개발하기 위해 구체적인 기능 설계와 구현을 미리 만들어 놓은 도구를 말한다.

디자인패턴은 객체 지향 프로그램을 어떻게 구현해야 좀 더 유연하고 재활용성이 높은 프로그램을 만들 수 잇는지를 정리한 내용이다.

단계 1: 생성자를 private으로 만들기

기본적으로 생성자를 안 만들면 컴파일러가 디폴트 생성자를 만들어 주는데, 이 생성자는 항상 public이다. public이면 외부 클래스에서 인스턴스를 여러 개 만들 수 있기에 이것을 방지하고자 명시적으로 private 생성자를 만들어 준다. 이렇게 하여 Company 클래스 내부에서만 클래스의 생성을 제어할 수 있다.

public class Company {
    pirvate Company() {}
}

단계 2: 클래스 내부에 static으로 유일한 인스턴스 생성하기

단계 1에서 외부 인스턴스를 만들 수 없게 하였다. 하지만 우리가 쓸 인스턴스 하나의 생성이 필요하다. 이 또한 private로 선언하여 외부에서 이 인스턴스에 접근하지 못하도록 제한한다.

public class Company {
    private static Company instance = new Company();
    private Company() {}
}

단계 3: 외부에서 참조할 수 있는 public 메서드 만들기

private의 접근을 위해 public 메서드 하나를 만들어 주고, 리턴으로 위에서 만들어진 인스턴스를 리턴해준다. 이 메서드는 static으로 만들어 주어야 한다. 그래야 인스턴스 생성과 상관없이 호출할 수 있기 때문이다.

public class Company {
    public static Company getInstance() {
        if(instance == null) {
            instance = new Company();
        }
        return instance;
    }
}

단계 4: 실제 사용

아래와 같이 하면 결과값으로 true가 뜰 것이다.

public class CompanyTest {
    public static void main(String[] args) {
        Company myCom1 = Company.getInstance();
        Company myCom2 = Company.getInstance();
        System.out.println(myCom1 == myCom2);
    }
}