# 6. 클래스

# 6.1 객체지향프로그래밍

  • 소프트웨어를 만들때 부품에 해당하는 객체를 먼저 만들고, 이 객체들을 하나씩 조립해서 완성된 프로그램을 만드는 기법

# 객체(object)란?

  • 물리적으로 존재하거나 개념적인 것 중에서 다른 것과 식별 가능한 것.
  • 객체는 속성(필드 field)과 동작(메소드 method)으로 구성된다.
  • 객체모델링(object modeling) : 현실 세계 객체의 대표 속성과 동작을 추려내어 소프트웨어 객체의 필드와 메서드로 정의하는 과정.

# 객체의 상호작용

메소드(매개값1, 매개값2, ...);
  • 메소드 : 객체들 간의 상호작용 수단. 객체가 다른 객체의 기능을 이용할때 메소드 호출한다.
  • 매개값 : 메소드 이름과 함께 전달하고자 하는 데이터. 메소드가 실행할때 필요한 값.
  • 리턴값 : 메소드의 실행의 결과이며, 호출한 곳으로 돌려주는 값 이다.

# 객체 간의 관계

대부분의 객체는 다른 객체와 관계를 맺고 있다.

# 집합관계

  • 완성품과 부품의 관계를 말한다.
  • 자동차 <- 엔진, 타이어, 핸들

# 사용관계

  • 다른 객체의 필드를 읽고 변경하거나 메소드를 호출하는 관계
  • 사람 -> 자동차 달린다, 멈춘다

# 상속관계

  • 부모와 자식 관계를 말한다.
  • 기계 <- 자동차

# 객체지향 프로그램의 특징

# 캡슐화(Encapsulation)

  • 객체의 데이터(필드), 동작(메소드)을 하나로 묶고 실제 구현 내용을 외부에 감추는 것.
  • 외부 객체는 객체 내부의 구조를 알지 못하며 객체가 노출해서 제공하는 필드와 메소드만 이용할 수 있다.
  • 필드와 메소드를 캡슐화하여 보호하는 이유는 외부의 잘못된 사용으로 인해 객체가 손상되지 않도록 하는데 잆다.
  • 접근제한자 : 캡슐화된 멤버를 노출시킬 것인지 숨길 것인지 결정

# 상속

  • 부모 객체는 자기가 가지고 있는 필드와 메소드를 자식 객체에게 물려주어 자식 객체가 사용할 수 있도록 한다.
  • 상속을 하는 이유
    # 코드의 재 사용성을 높여준다
    잘 개발된 부모 객체의 필드와 메소드를 자식이 그대로 사용할 수 있어 자식 객체에서 중복 코딩을 하지 않아도 된다.
    # 유지보수 시간을 최소화시켜 준다
    부모 객체의 필드와 메소드를 수정하면 모든 자식 객체들은 수정도니 필드와 메소드를 사용할 수 있다.

# 다형성

  • 사용 방법은 동일하지만 실행 결과가 다양하게 나오는 성질
  • 프로그램을 구성하는 객체(부품)를 바꾸면 프로그램의 실행 성능이 다르게 나올 수 있다.
  • 상속, 인터페이스 구현을 통해 얻어진다

# 6.2 객체와 클래스

  • 클래스 : 객체지향 프로그래밍에서 객체를 생성하는 설계도에 해당
  • 인스턴스 : 클래스로부터 생성된 객체
  • 인스턴스화 : 클래스로부터 객체를 만드는 과정

# 6.3 클래스 선언

  • 클래스 선언 : 객체 생성을 위한 설계도를 작성하는 작업.

    • 생성자 - 어떻게 객체를 생성할지
    • 필드 - 객체가 가져야 할 데이터가 무엇인지
    • 메소드 - 객체의 동작은 무엇인지 정의

public class 클래스명 {}
  • public class : 공개 클래스 선언
  • 공개 클래스 : 어느 위치에 있든지 패키지와 상관없이 사용할 수 있는 클래스.
  • 하나의 파일에 복수개의 클래스를 선언할때 소스 파일명과 동일한 클래스만 공개 클래스로 선언 가능.

# 6.4 객체 생성과 클래스 변수

클래스 변수 = new 클래스();
  • 객체생성연산자 new : 클래스로부터 객체생성시킨후 객체의 주소 리턴.

# 클래스의 두가지 용도

  • 라이브러리(library) 클래스 : 실행할 수 없으며 다른 클래스에서 이용하는 클래스
  • 실행 클래스 : main() 메소드를 가지고 있는 실행 가능한 클래스

일반적인 자바 프로그램은 하나의 실행 클래스와 여러개의 라이브러리 클래스로 구성됨. 실행 클래스는 실행하면서 라이브러리 클래스를 내부에서 이용한다

# 6.5 클래스의 구성 맴버(필드, 생성자, 메소드)

public class ClassName{
    int fieldName; // 필드 : 객체의 데이터가 저장되는곳
    ClassName() {...} // 생성자 : 객체 생성시 초기화 역할 담당
    int methodName() {...} // 메소드 : 객체의 동작으로 호출 시 실행하는 블록
}
1
2
3
4
5

# 6.6 필드 선언과 사용

구분 필드 (로컬)변수
선언위치 클래스선언블록 생성자, 메소드 선언블록
존재위치 객체 내부에 존재 생성자,메소드 호출시에만 존재
사용위치 객체내,외부 어디든사용 생성자,메소드 블록 내부에서만 사용
타입 필드명 [=초기값];
  • 타입 : 필드에 저장할 데이터의 종류를 결정.
  • 초기값을 제공하지 않을 경우 필드는 객체 생성 시 자동으로 기본값으로 초기화 된다.

# 필드사용

  • 클래스로부터 객체가 생성된 후에 필드 사용
  • 생성자와 메소드는 객체가 생성된 후 호출되므로 내부에서 필드 사용 할 수 있고, 객체 외부에서도 접근해서 사용할 수 있다.
  • 도트(.) 연산자 : 외부 객체에서 참조변수와 도트 연산자를 이용해서 필드를 읽고 변경. 객체가 가지고 있는 필드나 메소드 뒤에 붙여 객체가 가지고 있는 필드나 메소드에 접근

# 6.7 생성자 선언과 호출

클래스변수 = new 클래스(); // 생성자 호출
  • new 연산자는 객체를 생성한 후 연이어 생성자를 호출해서 객체를 초기화

# 기본생성자

[public] 클래스() {}
  • 모든 클래스는 생성자가 존재하며, 하나 이상 가질 수 있다.
  • 클래스에 생성자 선언이 없으면 컴파일러는 기본생성자(Default constructor)를 바이트코드 파일에 자동으로 추가시킨다.
  • 클래스가 public class 로 선언되면 기본 생성자도 public이 붙지만, 클래스가 public 없이 class 로만 선언되면 기본생성자에도 public 이 붙지 않는다.
  • 개발자가 명시적으로 선언한 생성자가 있다면 컴파일러는 기본 생성자를 추가하지 않는다.

# 생성자 선언

/** 생성자 블록 */
클래스(매개변수, ... ) {
    // 객체의 초기화 코드
}
  • 메소드 모양과 비슷하나 리턴 타입이 없고 클래스 이름과 동일.

# 필드 초기화

public class Korean {
    String nation = "대한민국";
    String name;
    String ssn;

    public Korean(String name, String ssn) {
        this.name = name;
        this.ssn = ssn;
    }
}
1
2
3
4
5
6
7
8
9
10

# 생성자 오버로딩

  • 생성자 오버로딩 : 매개변수의 타입, 개수, 순서를 달리하는 생성자를 여러 개 선언하는 것.
  • 매개변수의 타입, 개수, 선언된 순서가 똑같을 경우 컴파일 에러 발생

# 다른 생성자 호출

Car(String model) {
    this(model,"은색",250);
}
Car(String model, String color){
    this(model, color, 250);
}
Car(String model, String color, int maxSpeed){
    this.model = model;
    this.color = color;
    this.maxSpeed = maxSpeed;
}
1
2
3
4
5
6
7
8
9
10
11
  • this(매개값,...) : 생성자의 첫 줄에 작성되며 다른 생성자를 호출하는 역할을 한다. 호출하고 싶은 생성자의 매개변수에 맞게 매개값을 제공하면 된다.

# 6.8 메소드 선언과 호출

  • 메소드 선언 : 객체의 동작을 실행 블록으로 정의. 객체간의 상호작용 하는 방법 정의
  • 메소드 호출 : 타입변수 = 메소드();

# 가변길이 매개변수

int sum(int ... values) {}

int result = sum(1, 2, 3);
int result = sum(1, 2, 3, 4, 5);

int[] values = {1,2,3};
int result = sum(values);
int result = sum(new int[] {1,2,3});

# 메소드 오버로딩

  • 메소드 이름은 같되 매개변수의 타입, 개수, 순서가 다른 메소드를 여러개 선언

# 6.9 인스턴스 멤버

구분 설명
인스턴스(instance)멤버 객체에 소속된 멤버(객체를 생성해야만 사용)
정적(static)멤버 클래스에 고정된 멤버(객체 없이도 사용)
public class Car {
    int gas; // 인스턴스 필드 선언
    void setSpeed(int speed){...} // 인스턴스 메소드 선언
}
1
2
3
4
  • gas 필드는 객체에 소속된 멤버가 분명하지만, setSpeed() 메소드는 객체에 포함되지 않는다.
  • 메소드는 코드의 덩어리 이므로 객체마다 저장한다면 중복 저장으로 인해 메모리 효율이 떨어진다.
  • 따라서 메소드 코드는 메소드 영역에 두되 공유해서 사용하고, 이때 객체 없이는 사용하지 못하도록 제한을 걸어둔 것이다.
  • this 키워드 : 인스턴스 필드임을 강조하기 위해

# 6.10 정적 멤버

  • 자바는 클래스 로더를 이용해서 클래스를 메소드 영역에 저장하고 사용한다.
  • 정적(static) 멤버 : 메소드 영역의 클래스에 고정적으로 위치하는 멤버. 정적 멤버는 객체를 생성할 필요 없이 클래스를 통해 바로 사용이 가능하다.

# 정적 멤버

public class 클래스 {
    // 정적 필드 선언
    static 타입 필드 [=초기값];
    // 정적 메소드
    static 리턴타입 메소드 (매개변수, ...) {...}
}
  • 객체마다 가지고 있을 필요성이 없는 공용적인 필드는 정적 필드로 선언하는 것이 좋다. ex. 파이(3.14...)
  • 인스턴스 필드를 이용하지 않는 메소드는 정적 메소드로 선언하는 것이 좋다.
  • 클래스가 메모리로 로딩되면 정적 멤버를 바로 사용할 수 있다.
  • 정적 요소는 클래스 이름으로 접근하는 것이 정석이다.

# 정적 블록

static int field;
static void method(){}
// 정적 블록 선언
static {
    field = 10;
    method();
}
1
2
3
4
5
6
7
  • 복잡한 초기화 작업.
  • 정적 블록은 클래스가 메모리에 로딩될 때 자동으로 실행된다. 정적 블록이 클래스 내부에 여러개가 선언되어 있을 경우에는 선언된 순서대로 실행된다.
  • 정적 필드는 생성자에서 초기화 작업을 하지 않는다. 생성자는 객체 생성 후 실행되기 때문
  • 정적 메소드와 정적 블록은 객체가 없어도 생성된다는 특징 때문에 내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다. 또한 객체 자신의 참조인 this도 사용할 수 없다.
public static void main(String[] args){
    className obj = new ClassName();
    obj.field1 = 10;
    obj.method1();
}
1
2
3
4
5

# 6.11 final 필드와 상수

# final 필드 선언

  final 타입 필드 [=초기값];
  • final 필드는 초기값이 저장되면 최종적인 값이 되어 프로그램 실행 도중에 수정할 수 없게 된다.
  • final 필드에 초기값을 줄 수 있는 방법
    1. 필드 선언 시에 초기값 대입 (고정된값)
    2. 생성자에서 초기값 대입 (복잡한 초기화코드, 객체 생성시 외부에서 전달된 값으로 초기화)
  • 이 두가지 방법을 사용하지 않고 final 필드를 그대로 남겨두면 컴파일 에러가 발생하게 된다.

# 상수 선언

static final 타입 상수 [= 초기값];
  • 상수 : 객체마다 저장할 필요가 없고, 여러개의 값을 가져도 안됨.
  • 선언 시에 초기화 하는 것이 일반적이지만, 복잡한 초기화가 필요할 경우 정적 블록에서 초기화 할 수도 있다.
  • 상수 이름은 대문자로 작성하는것이 관례이다.
  • 상수는 정적 필드 이므로 클래스로 접근해서 읽을 수 있다.

# 6.12 패키지

  • 자바의 패키지(package) : 클래스의 일부분이며, 클래스를 식별하는 용도로 사용된다. 주로 개발 회사의 도메인 이름의 역순으로 만든다.
  • 패키지는 클래스를 식별하는 용도로 사용되기 때문에 클래스의 전체 이름에 포함된다.
  • 패키지에 속한 바이트코드파일(~.class)은 따로 떼어내어 다른 디렉토리로 이동할 수 없다.

# 패키지 선언

package 상위패키지.하위패키지;

public class 클래스명 [...]
  • 패키지 디렉토리는 클래스를 컴파일 하는 과정에서 자동으로 생성된다
  • 컴파일러는 클래스의 패키지 선언을 보고 디렉토리를 자동 생성 시킨다.
  • 도메인 역순 + 프로젝트 이름
  • 소스 파일(~.java)이 저장되면 이클립스는 자동으로 컴파일 해서 <blahblah>/bin 디렉토리에 패키지 디렉토리와 함께 바이트코드 파일(~.class)을 생성한다.
  • 만약 패키지 선언이 없다면 이클립스는 클래스를 (default package)에 포함시킨다. default package란 패키지가 없다는 뜻 이다.

# import문

  • 같은 패키지에 있는 클래스는 아무런 조건 없이 사용할 수 있지만, 다른 패키지에 있는 클래스를 사용하려면 import 문을 이용해서 어떤 패키지의 클래스를 사용하는지 명시해야 한다.
  • import문이 작성되는 위치는 패키지 선언과 클래서 선언사이다.
  • import 키워드 뒤에는 사용하고자 하는 클래스의 전체 이름을 기술한다.
  • 동일한 패키지에 포함된 다수의 클래스를 사용해야 한다면 클래스 이름을 생략하고 * 사용.
  • import문은 하위 패키지를 포함하지 않는다.
  // 둘다 사용해야 한다면 두개의 import 문이 필요하다.
  import com.hankook.*;
  import com.hankook.project.*;
1
2
3
  • 서로 다른 패키지에 동일한 클래스 이름이 존재하는 경우 클래스의 전체 이름 사용
com.hankook.Tire tire = new com.hankook.Tire();
1

# 이클립스 import 자동 추가 기능

  1. import 전체클래스이름 : Ctrl + shift + O
  2. import 패키지.* : preference 설정 변경 code style > organize imports > number of imports needed for.* 의 99 를 1로 변경

# 6.13 접근제한자

  • 객체의 필드를 외부에서 변경하거나 메소드를 호출 할 수 없도록 막아야 할 필요가 있다.
  • default : 접근 제한자가 붙지 않는 상태
접근제한자 제한대상 제한범위
public 클래스, 필드, 생성자, 메소드 없음
protected 필드, 생성자, 메소드 같은 패키지이거나, 자식 객체만 사용 가능
(default) 클래스, 필드, 생성자, 메소드 같은 패키지
private 필드, 생성자, 메소드 객체 내부

# 클래스의 접근 제한

[public] class 클래스 {...}
  • public 과 default 접근 제한을 가질 수 있다
  • public을 생략하면 default 접근 제한을 가짐
  • default : 같은 패키지 내에서는 아무런 제한 없이 사용할 수 있지만 다른 패키지에서는 사용할 수 없다
  • public : 같은 패키지 뿐만 아니라 다른 패키지에서도 사용 가능

# 생성자의 접근 제한

public class ClassName {
    // 생성자 선언
    [public | private] ClassName(...) {...}
}
접근 제한자 생성자 설명
public 클래스(...) 모든 패키지에서 생성자를 호출할 수 있다
=모든 패키지에서 객체를 생성할 수 있다
클래스(...) 같은 패키지 에서만 생성자를 호출할 수 있다
=같은 패키지 내에서만 객체를 생성할 수 있다
private 클래스(...) 클래스 내부에서만 생성자를 호출할 수 있다
=클래스 내부에서만 객체를 생성할 수 있다

# 필드와 메소드의 접근 제한

// 필드 선언
[ public | private ] 타입 필드;
// 메소드 선언
[ public | private ] 리턴타입 메소드(...) {...}
접근제한자 생성자 설명
public 필드
메소드(...)
모든 패키지에서 필드를 읽고 변경할 수 있다
모든 패키지에서 메소드를 호출할 수 있다
필드
메소드(...)
같은 패키지에서만 필드를 읽고 호출할 수 있다
같은 패키지에서만 메소드를 호출할 수 있다
private 필드
메소드(...)
클래스 내부에서만 필드를 읽고 변경할 수 있다
클래스 내부에서만 메소드를 호출할 수 있다

# 6.14 Getter 와 Setter

  • 객체의 필드(데이터)를 외부에서 마음대로 읽고 변경할 경우 객체의 무결성(결점이 없는 성질)이 깨질 수 있다.
  • 이러한 문제점 때문에 객체 지향 프로그래밍에서는 직접적인 외부에서의 필드 접근을 막고 대신 메소드를 통해 필드에 접근하는 것을 선호
private 타입 fieldName;

//Getter
public 타입 getFildName() {
    return fieldName;
}
//Setter
public void setFieldName(타입 fieldName) {
    this.fieldName = fieldName;
}
1
2
3
4
5
6
7
8
9
10
  • 필드 타입이 boolean 인 경우 is로 시작하는것이 관례.

# 6.15 싱글톤 패턴

  • 애플리케이션 전체에서 단 한 개의 객체만 생성해서 사용
  • 생성자를 private 접근 제한해서 외부에서 new 연산자로 생성자를 호출할 수 없도록 막는 것 이다.
  • 생성자를 호출할 수 없으니 외부에서 마음대로 객체를 생성하는 것이 불가능하다 대신 싱글톤 패턴이 제공하는 정적 메소드를 통해 간접적으로 객체를 얻을 수 있다.
public class 클래스 {
    // private 접근 권한을 갖는 정적 필드 선언과 초기화
    private static 클래스 singleton = new 클래스();
    // private 접근 권한을 갖는 생성자 선언
    private 클래스() {}
    // public 접근 권한을 갖는 정적 메소드 선언
    public static 클래스 getInstance() {
        return singleton;
    }
}
1
2
3
4
5
6
7
8
9
10
  • 외부에서 객체를 얻는 유일한 방법은 getInstance() 메소드를 호출하는 것 이다.

  • getInstance() 메소드가 리턴하는 객체는 정적 필드가 참조하는 싱글톤 객체이다.

    클래스 변수1 = 클래스.getInstance();
    클래스 변수2 = 클래스.getInstance();