Java

이것이 자바다 6장 [클래스] (1) - 개념정리 및 확인문제

코드사냥꾼 2019. 11. 11. 15:02

클래스에 대해 자세히 이해하기 위해서는 우선 객체라는 것에 대해 숙지를 해야 한다. 

1. 객체

객체란 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신의 속성을 가지고 있고 다른 것과 명확하게 식별 가능한 것을 말한다. 사람이 '이름, 나이'와 같은 속성과 '웃다, 걷다' 등의 동작이 존재하듯이 객체도 속성동작으로 구분되어 있다. 자바에서는 객체의 속성필드 그리고 동작메소드라고 부른다.

2. 객체 간의 상호작용

객체들은 각각 독립적으로 존재하고, 다른 객체와 서로 상호작용한다. 이때 상호작용의 수단은 메서드이며 객체가 다른 객체의 기능을 이용하는 것이 메서드 호출이다. 정리하자면 객체의 상호작용은 객체 간의 메서드 호출을 의미하며 매개 값과 리턴 값을 통해서 데이터를 주고받는 것이다.

// 매개값으로 준 10,20을 주고난 후 연산을 통해 리턴한 값을 int 변수에 저장한다
int result = Calculator.add(10,20);

3. 객체 지향 프로그래밍의 특징

객체를 사용하는 프로그램을 객체 지향 프로그래밍(OOP)이라고 한다. 객체 지향 프로그램의 특징으로는 캡슐화, 상속, 다형성을 들 수 있다.

  • 캡슐화 : 객체의 필드, 메서드를 하나로 묶고 실제 구현 내용은 감추는 것을 말한다. 외부 객체는 객체 내부의 구조를 알지 못한다. 캡슐화하여 필드와 메소드를 보호하는 이유는 외부의 실수로 인해 객체가 손상되지 않도록 하는데 있다. 하지만 자바 언어는 다양한 접근 제한자를 사용하여 객체의 필드와 메소드의 사용범위를 제한함으로 유연하게 보호한다.

  • 상속 : 객체 지향 프로그래밍에서 부모 역할의 상위 객체와 자식 역할의 하위 객체가 존재한다. 상위 객체는 자신이 가지고 있는 필드와 메소드를 하위 객체가 사용할 수 있도록 해준다. 즉, 상속이란 하위 객체가 상위 객체를 재사용하여 새로운 객체를 만들어 코드의 중복을 줄여주고 개발 시간을 절약시켜 주는 좋은 기능이다. 또한 상속은 상위 객체의 수정으로 모든 하위 객체들의 수정 효과를 가져오므로 유지 보수 시간을 최소화시켜준다.

  • 다형성 : 다형성은 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질을 말한다. 자바는 다형성을 위해 부모 클래스 또는 인터페이스의 타입 변환을 허용한다. 부모 타입에는 모든 자식 객체가 대입될 수 있고, 인터페이스 타입에는 모든 구현 객체가 대입될 수 있다. 다형성의 효과로 객체는 부품화가 가능하다. 예를 들면 자동차는 타이어 타입으로 한국 타이어와 금호 타이어를 사용하지만 각 타이어의 성능은 다르게 나온다.

4. 객체와 클래스

객체를 사용하기 위해서는 설계도를 이용하여 사용하고 싶은 객체를 만들어야 한다. 자바에서는 이 설계도가 바로 클래스이다. 클래스에는 객체를 생성하기 위한 필드와 메서드로 정의되어있다. 클래스로부터 만들어진 객체를 해당 클래스의 인스턴스라고 한다.

5. 클래스 선언

// 클래스 생성 기본구조
public class 클래스이름 {

}

// ex) Car.java
public class Car {

}

class Tire {

}

일반적으로 소스 파일당 하나의 클래스를 선언한다. 하지만 두 개 이상의 클래스 선언도 가능하다. 두  개 이상의 클래스가 선언된 소스 파일을 컴파일하면 바이트 코드 파일은 클래스를 선언한 개수만큼 생긴다. 위의 코드를 컴파일하면 Car.class 와 Tire.class 각각 생성되는 것이다. 여기서 주의할 점은 파일 이름과 동일한 이름의 클래스 선언에만 public 접근 제한자를 붙일 수 있다. 가급적이면 소스파일(. java) 하나당 동일한 이름의 클래스 하나를 선언하는 것이 매우 바람직하다.

6. 객체 생성과 클래스 변수

클래스로부터 객체를 생성하는 방법은 new 연산자를 사용하면 된다. new 연산자로 생성된 객체는 메모리 힙(heap) 영역에 생성되어 객체의 주소를 리턴하도록 되어있다. 따라서 비록 같은 클래스에서 생성되었다 하더라도 각각의 객체는 자신만의 고유 데이터를 가지게 되면서 완전히 독립된 서로 다른 객체가 된다.

7. 클래스의 구성

 

클래스를 구성하는 필드, 생성자, 메서드는 생략되거나 복수 개가 작성될 수 있다.

  • 필드 : 객체의 고유 데이터, 상태 정보를 저장하는 곳이다. 얼핏 변수와 비슷하지만 필드는 생성자와 메소드 전체에서 사용되며 객체가 소멸되지 않는 한 객체와 함께 존재하는 점에서 변수와 다르기 때문에 변수라고 부르지 않는다. 

// 올바르게 필드를 선언한 예
// Car 클래스 필드 선언

public class Car {
  String company = "현대";
  String model = "그랜저";
  int maxSpeed = 300;
}
// 외부 클래스에서 Car 필드값 읽기와 변경

public class CarExample {
  public static void main(String args[]) {
     // 객체 생성
     Car c = new Car();
     
     // 필드값 읽기
     System.out.println("회사 : " + c.company);
     System.out.println("모델 : " + c.model);
     System.out.println("최고속도 : " + c.maxSpeed);
     
     // 필드값 변경
     c.maxSpeed = 200;
     System.out.println("수정된 최고속도 : " + c.maxSpeed);
  }
}
  • 생성자 : 생성자의 역할은 객체 생성 시 초기화를 담당한다. 하나 이상을 가질 수 있고 필드를 초기화하거나, 메서드를 호출해서 객체를 사용할 준비를 하며 리턴 타입이 없고 클래스 이름과 동일하다.  객체 생성 시점에 외부에서 제공되는 다양한 값들로 초기화되어야 한다면 생성자에서 초기화를 해야 한다. 이름과 주민번호와 같은 필드값은 클래스를 작성할 때 다양한 사람들의 정보를 얻기 위해서 객체 생성 시점에 다양한 값을 가져야한다. 

// 객체 생성 시점에 다양한 값을 가져야 할 때 사용하는 생성자를 이용한 초기화 예제

public class Korean{
  // 필드
  String nation = "대한민국";
  String name;
  String ssn;
  
  // 생성자
  public Korean(String n, String s) {
    name = n;
    ssn = s;
  }
}

      ** 생성자 오버로딩 : 생성자의 다양화를 위해 매개 변수를 달리하는 생성자를 여러 개 선언하는 것 **

// 옳은 오버로딩 예
public class Car {
  Car() { }
  Car(String color) { }
  Car(String color, String model) { }
  Car(String color, String model, int maxSpeed) { }
}

// 잘못된 오버로딩 예
public class Car {
  Car(String color, String model) { }
  Car(String model, String color) { }
}

// this() 를 사용하여 중복 코드 줄이기
public class Car {

  // 필드 선언
  String company = "현대";
  String model;
  String color;
  int maxSpeed;
  
  // 기본 생성자 선언
  Car() { }
  
  Car(String model) {
    this(model, "은색", 250); 
  }
  
  Car(String model, String color) {
    this(model, color, 250); 
  }
   
  Car(String model, String color, int maxSpeed) {
    this(model, color, maxSpeed); // 공통 실행 코드
  }
}
  • 메소드 : 객체의 동작에 해당하는 중괄호 블록을 말한다.  즉, 메서드를 호출하게 되면 중괄호 블록에 있는 모든 코드들이 일괄적으로 실행된다. 메서드는 필드를 읽고 수정하는 역할도 하지만 다른 객체 생성해서 다양한 기능을 수행하기도 하며 객체 간의 데이터 전달의 수단으로 사용된다. 외부로부터 매개 값을 받을 수도 있고, 실행 후 값을 리턴하기도 한다.