본문으로 바로가기

클래스는 객체를 생성하기 위한 설계도로 프로그래머는 객체로부터 필요한 정보를 추출하여 클래스를 설계한다. 이때 객체가 가지는 속성(정보)과 행동(기능)을 클래스로 정의하는 과정을 추상화(Abstraction)라고 한다.

사람 객체를 추상화하는 과정을 거쳐 Person 클래스로 정의해보자. 사람이라는 객체는 현실에서 많은 정보를 가지고 있지만 특별히 이름과 나이라는 속성과 소개하다와 같은 행동을 추출하여 다음과 같이 클래스를 설계할 수 있다.

public class Person {
  String name;
  int age;

  void introduce() {
    System.out.println("Person[name: " + name + ", age: " + age + "]");
  }
}

이때 이름이나 나이와 같은 속성을 클래스의 필드라 칭하고 소개하다와 같은 행동을 클래스의 메소드라 칭한다. 위에서 설계한 클래스는 아래와 같이 사용할 수 있다.

Person person = new Person();
person.name = "Arsene Wenger";
person.age = 69;
person.introduce();// Person[name: Arsene Wenger, age: 69]

생성자

클래스를 이용하여 객체를 생성할 때 자동적으로 생성자가 호출된다. 클래스는 기본적으로 기본 생성자를 가지고 있고 사용자가 따로 생성자를 정의하지 않아도 기본적으로 객체가 생성되면 기본 생성자가 호출이 된다.

생성자는 객체가 생성될 당시 객체의 정보를 초기화하는 작업을 주로 담당하는데 사용자는 임의의 생성자를 따로 정의할 수 있다. 단, 사용자가 임의로 새로운 생성자를 정의하는 경우 기본 생성자는 자동적으로 사라진다는 점을 유의하자.

이제 Person 클래스에 이름과 나이를 생성과 동시에 원하는 값으로 할당할 수 있도록 새로운 생성자를 정의해보자.

public class Person {
  String name;
  int age;

  Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  void introduce() {
    System.out.println("Person[name: " + name + ", age: " + age + "]");
  }
}

새로운 생성자를 정의하는 순간 기존에 사용했던 기본 생성자가 사라지기 때문에 Person person = new Person()과 같은 형태로 객체를 생성할 수 없다. 대신 사용자가 원하는 값을 사용하여 아래와 같이 보다 더 편리한(사용자가 원하는) 방식으로 객체를 생성할 수 있다.

Person person = new Person();// error occurred
Person person = new Person("Arsene Wenger", 69);
person.introduce();// Person[name: Arsene Wenger, age: 69]

this 객체

위에서 사용자가 임의로 정의한 생성자의 내부 코드를 살펴보면 this라는 키워드가 사용된 걸 볼 수 있다. this는 객체 자신을 가리키는데 자신을 호출한 객체에 바인딩된다.

Person p1 = new Person("Person1", 1);
Person p2 = new Person("Person2", 2);

위와 같은 경우 p1 객체가 생성될 때에는 thisp1 객체를 가리키기 때문에 name 필드에 Person1이라는 값이 할당되고 p2 객체가 생성될 때에는 마찬가지로 name 필드에 Person2라는 값이 할당된다.

클래스(Class) 설계

이제 위에서 정의한 Person 객체를 조금 더 잘 설계해보자. 객체가 name이나 age와 같은 필드에 직접적으로 접근하는 것은 상당히 위험할 수 있다. 특히 나이라는 정보를 가지느 age 변수에 음수 값이 들어가는 것은 때때로 심각한 프로그램 오류를 야기할 수 있기 때문에 프로그래머는 name이나 age와 같은 변수를 private으로 설정하는 것이 일반적이다. private로 지정된 필드는 사용자가 임의로 접근할 수 없기 때문에 사용자는 임의로 필드의 값을 읽을 수 있는 getter 메소드와 setter 메소드를 public으로 설정하여 정의한다.

이제 Person 클래스의 필드 값을 일반 사용자가 접근할 수 없도록 private로 지정하고 해당 필드에 접근할 수 있도록 getter/setter 메소드를 public으로 정의해보자.

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

  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    if (age < 0) {
      System.out.println("Invalid age value!");
    }
    this.age = age;
  }

  public void introduce() {
    System.out.println("Person[name: " + name + ", age: " + age + "]");
  }
}

위처럼 클래스를 정의하면 더이상 private 접근 제한자를 가지는 필드에 직접적으로 접근할 수 없게 되고 사용자는 getter 메소드나 setter 메소드를 이용해서만 필드에 접근할 수 있다.

Person person = new Person("Arsene Wenger", 69);
person.name = "Unai Emery";// error occurred
person.age = 47;// error occurred
person.setName("Unai Emery");
person.setAge(-47);// Invalid age value!
person.setAge(47);
person.introduce();// Person[name: Unai Emery, age: 47]
System.out.println("Person[name: " + person.getName() + ", age: " + person.getAge() + "]");// Person[name: Unai Emery, age: 47]

접근 제한자

접근 제한자는 아래와 같이 public, protected, default, private 총 4개로 이루어져 있으며 명시적으로 지정할 수 있는 다른 접근 제한자와는 달리 default 접근 제한자는 필드나 메소드 앞에 아무 키워드도 붙지 않을 경우에 적용된다.

접근 제한자 종류 접근 범위
public 모든 접근을 허용
protected 같은 패키지 혹은 상속 관계에 있는 클래스에게만 허용
default 같은 패키지만 허용
private 외부에서 접근 불가

Static

클래스는 static 키워드로 선언된 정적 필드나 정적 메소드를 가질 수 있는데 일반적으로 정적 필드나 메소드는 객체 단에서 호출하는 것이 아니라 클래스 자체에서 호출하는 것이 일반적이다.

예를 들어 원이라는 도형을 추상화한 Circle 클래스에서 원주율을 정의한 PI 변수를 생각해보자. PI라는 변수는 생성된 원 객체마다 가질 필요 없이 클래스 단에서 딱 한 번만 정의하면 되는데 이때 static 키워드를 사용하여 정의한 PI 변수를 정적 필드라 한다.

또한 계산기를 추상화한 Calculator 클래스 같은 경우 더하거나 곱하는 등의 연산 메소드를 정의할 수 있는데 이 역시 클래스 단에서 호출하는 편이 더 효율적이다. 이때 static 키워드를 사용하여 정의된 메소드를 정적 메소드라 한다.

System.out.println("PI: " + Math.PI);// PI: 3.141592653589793
System.out.println(Math.abs(-23));// 23

댓글을 달아 주세요