본문 바로가기
전공 수업/객체 지향 프로그래밍(Java)

[13주 차] - 제어자, 접근 제어자, 캡슐화, 다형성

by TwoJun 2022. 11. 27.

    과목명 : 객체지향 프로그래밍(Object oriented programming)

수업일자 : 2022년 11월 23일 (수)

 

 

 

 

 

1. 제어자(Modifier)

1-1. 제어자의 정의

- 클래스와 클래스의 멤버(변수, 메서드)를 선언할 때 사용되며 이들에 대해 부가적인 의미를 부여해 주는 키워드입니다.

 

 

 

 

1-2. 제어자의 특징

(1) 크게 접근 제어자(Access modifier), 제어자(Modifier)로 나뉩니다.

 

(2) 하나의 대상에 여러 개의 제어자를 조합해서 사용 가능하나, 접근 제어자는 단 한 개만 사용할 수 있습니다.

관례적으로 접근 제어자를 맨 앞에, 뒤에 일반적인 제어자를 붙여주는 형식으로 코드를 작성합니다.

 

 

 

 

1-3. 제어자의 여러 가지 종류

(1) 접근 제어자(Access modifier)

- public, protected, (default), private

 

(2) 그 외 일반 제어자

- static, final, abstract, native, transient, synchronized, volatile, strictfp

 

 

 

 

1-4. static

(1) 정의

- 제어자 static은 "공통적인"이라는 뜻을 가지고 있는 제어자입니다.

 

(2) 사용될 수 있는 곳 

- 멤버 변수, 메서드, 초기화 블럭

 

제어자 사용 가능 대상 특징
static 멤버 변수 - 생성된 모든 인스턴스에 공통적으로 사용 가능한 클래스 변수

- 클래스 변수는 객체(클래스의 인스턴스)를 생성하지 않고도 사용 가능

- 클래스가 메모리에 로드될 때 생성
메서드 - 인스턴스를 생성하지 않고도 메서드 호출이 가능한 Static 메서드

- Static 메서드 내부에서는 인스턴스 멤버(인스턴스 변수, 인스턴스 메서드)를 사용할 수 없음

 

class StaticModifierTest {
    // 자의 공통적인 특성을 나타내는 길이, 소재(클래스 변수)
    static int length = 10;
    static String material = "plastic";
    
    // 자의 공통적인 기능 - 길이 측정 (static 메서드)
    static void calculateLength(int length) {
        System.out.println("해당 사물의 길이는 " + length + "cm입니다.");
    }
    
    // 클래스 초기화 블럭 - 클래스 변수의 초기화 수행
    static {
        // 내용 생략;
    }
}

 

 

 

 

1-5. final

(1) 정의

- 제어자 final은 "마지막의, 변경 불가능한" 이라는 뜻을 가진 제어자입니다.

 

(2) 사용될 수 있는 곳

- 클래스, 메서드, 멤버변수, 지역변수

 

제어자 사용 가능 대상 특징
final 클래스 - 변경 불가능한 클래스가 되며, 확장 불가능한 클래스가 된다.

- 해당 클래스는 다른 클래스의 부모(조상) 클래스가 될 수 없다.
메서드 - 변경 불가능한 메서드가 되며, 해당 메서드는 메서드 오버라이딩이 불가능하다.
멤버 변수 - 변수 앞에 final이 붙은 경우 해당 변수는 변경 불가능한 상수값이 된다.
지역 변수

 

 

 

 

1-6. abstract

(1) 정의

- 제어자 abstract은 "추상적인, 미완성된" 이라는 뜻을 가진 제어자입니다.

 

(2) 사용될 수 있는 곳

- 클래스, 메서드 

 

(3) 추상 클래스(Abstract class)

- 하나 이상의 추상 메서드를 가지고 있는 클래스를 의미합니다.

 

(4) 추상 메서드(Abstract method)

- 아직 완성되지 않은 메서드를 의미하며 선언부는 완성되어 있으나 구현부가 정의되지 않은 메서드입니다.

 

- 추상 클래스와 추상 메서드는 이후 포스팅에서 자세히 한 번 더 다뤄보도록 하겠습니다.

제어자 사용 가능 대상 특징
abstract 클래스  - 클래스 내부에 추상 메서드가 선언되어 있는 클래스임을 나타낸다. (미완성 클래스, 추상 클래스)
메서드 - 선언부는 정의되어 있으나 구현부가 정의되지 않은 메서드임을 나타낸다. (미완성 메서드, 추상 메서드)

 

// 추상 클래스
abstract class AbstractClassTest {
    // 추상 메서드
    abstract void abstractMethod();
}

 

 

 

 

 

 

 

2. 접근 제어자(Access modifier), 캡슐화(Encapsulation)

2-1. 접근 제어자의 정의

- 클래스와 멤버(변수, 메서드)에 사용되며 외부에서 해당 클래스나 메서드에 대해 접근 권한을 설정할 수 있는 제어자를 의미합니다.

 

 

 

 

2-2. 접근 제어자의 종류 

접근 제어자 특징
private 같은 클래스 내에서만 접근이 가능하다.
(default) 같은 패키지 내에서만 접근이 가능하다.
protected 같은 패키지 내부다른 패키지의 자식 클래스에서 접근이 가능하다.
public 접근 제한이 존재하지 않음

 

 

 

 

2-3. 접근 제어자의 접근 가능 범위

제어자 동일 클래스 동일 패키지 동일 패키지 내부, 
다른 패키지의 자식 클래스
제한 없음
public O O O O
protected O O O X
(default) O O X X
private O X X X

 

 

 

 

2-4. 접근 제어자를 이용한 캡슐화(Encapsulation)

(1) 캡슐화(Encapsulation)

객체지향 프로그래밍(Object Oriented Programming, OOP)의 4대 원칙 중 하나로써, 객체의 속성(데이터 필드 - 변수)과 다양한 기능(메서드)을 하나로 묶고 실제 기능들의 구현 방법을 외부로부터 감추는 것을 의미합니다.

 

- 이러한 특성 때문에 *정보 은닉(Information hiding)의 개념도 포함하고 있습니다.

 

- * 정보 은닉(Information hiding) : 외부에서 객체의 속성에 대해 접근할 수 없도록 막는 것

 

 

(2) 접근 제어자를 사용하는 이유

- 쉽게 말하면 외부로부터 데이터를 보호하기 위해 사용합니다.

 

- 외부에서는 불필요한 정보(특정 기능에 대한 구현 방법), 내부적으로 특정 기능을 구현하기 위해 사용되는 로직 부분을 감추기 위해 사용합니다.

public class TimeWatch {
    // 외부로부터 발생하는 직접적인 데이터 접근을 막는다
    private int hour;      // 0~24
    private int minute;    // 0~59
    private int second;    // 0~59
    
    // 단, 메서드를 통해 직접 접근하는 방법은 허용한다.
    
    /* 시계 특성 상, 임의의 수가 들어오는 건 허용될 수 없으므로
    메서드를 통해 직접 값을 넘기도록 하고, 단 원하는 값이 아닐 경우엔
    값을 저장할 수 없도록 하고 유효한 값만 저장될 수 있도록 한다 */
    public int getHour() { return hour; }
    
    public void setHour(int hour) {
        if (hour < 0 || hour > 23) 
        	return;
        else
            this.hour = hour;
    }
}

 

 

 

 

2-5. 생성자의 접근 제어자

- 일반적으로 생성자의 접근 제어자는 클래스의 접근 제어자와 일치하는 특성을 가집니다.

 

- 생성자에 접근 제어자를 사용함으로써, 객체 생성을 방지할 수 있습니다.

class FixedUser {
    /* 현재 수용 인원은 100명이고 관리자에 여건에 따라 최대 수용인원을
     수정할 수 있음, 외부에선 수용 인원 수정은 불가능하다. */
    int USER_NUMBER = 100;

    // 외부에서 객체를 생성할 수 없도록 한다.
     private FixedUser(int modifyUserNumber) {
        USER_NUMBER = modifyUserNumber;
    }
}
public class FixedUserNumber {
    public static void main(String[] args) {
        // 최대 인원을 바꾸고자 객체 생성 시 오류 발생
        FixedUser fu = new FixedUser(30);  // java: FixedUser(int) has private access in FixedUser
    }
}

 

 

 

 

2-6. 제어자 조합 원칙

사용 가능 대상 사용 가능한 제어자
클래스 public, (default), final, abstract
메서드 모든 접근 제어자, final, abstract, static
멤버 변수 모든 접근 제어자, final, static
지역 변수 final

(1) 메서드에 static, abstract를 함께 사용할 수 없습니다.

- 메서드에서 static은 구현부가 존재할 때만 사용 가능하기 때문입니다.

 

 

(2) 클래스에 abstract, final을 동시에 사용할 수 없습니다.

- 클래스에 사용되는 final은 더 이상 클래스를 확장할 수 없는 클래스임을 나타내며, 클래스에서 abstract는 상속을 통해 완성되어야 한다는 의미를 가지므로 서로 의미가 모순되기에 동시에 사용할 수 없게 됩니다.

 

 

(3) abstract 메서드의 접근 제어자가 private일 수 없습니다.

- abstract 메서드일 땐 자식 클래스에서 구현해야 하는데 접근 제어자가 private이면 자식 클래스에서 부모 클래스에 접근할 수 없기 때문입니다.

 

 

(4) 메서드에 private, final을 동시에 사용할 필요는 없습니다.

- 접근 제어자가 private인 메서드는 오버라이딩할 수 없으므로 private, final 둘 중 하나만 사용되어야 합니다.

 

 

 

 

 

 

3. 다형성(Polymorphism)

3-1. 다형성(Polymorphism)의 정의

- 객체지향 프로그래밍(Object Oriented Programming, OOP)의 4대 원칙 중 하나로써 특정 객체가 여러 가지 타입을 가질 수 있는 것을 의미하며, 부모(조상) 클래스 타입의 참조변수로 자식 클래스 타입의 객체를 다룰 수 있는 것을 말합니다.

 

 

 

 

 

3-2. 참조 변수의 형 변환 (Type casting of reference variable)

- 생성된 객체에서 사용할 수 있는 멤버(변수, 메서드)의 개수를 조절하기 위해 사용되는 타입 변환입니다.

 

- 서로 상속 관계(부모 ↔ 자식)에 있는 클래스 사이에서만 형 변환이 가능합니다.

 

- 사용할 수 있는 멤버의 개수를 조절할 때 사용되나 기존 클래스의 멤버에 대한 정보는 수정할 수 없습니다.

 

 

 

 

 

3-3. 업캐스팅(Upcasting), 다운캐스팅(Downcasting)

(1) 업캐스팅(Upcasting) 

- 자손 타입에서 부모(조상) 타입으로의 형 변환이며 생략 가능합니다.

 

(2) 다운캐스팅(Downcasting)

- 부모(조상) 타입에서 자손 타입으로의 형 변환이며 생략 불가능하고, 반드시 형 변환을 명시해야 합니다.

 

- 아래 예시 코드를 통해 확인해 보겠습니다.

class GeneralCar {

    // 공통 속성 - 운전
    void drive() { System.out.println("drive!"); }

    // 공통 속성 - 정지
    void stop() { System.out.println("Stop!"); }
}

// 소방차 클래스 정의
class FireFighterCar extends GeneralCar {

    // 소방차의 기능 : 물 뿌리기
    void water() { System.out.println("Water!"); }

    // 소방차의 기능 : 사이렌 울리기
    void siren() { System.out.println("Siren!"); }
}

// 구급차 클래스 정의
class AmbulanceCar extends GeneralCar {

    // 구급차의 기능 : 사이렌 울리기
    void siren() { System.out.println("Siren!"); }

}

class TypecastingReferenceVariableTest {
    public static void main(String[] args) {
        GeneralCar gc = null;
        FireFighterCar f1 = new FireFighterCar();
        FireFighterCar f2 = null;

        f1.water();      // Water!
        
        // up-casting으로 형 변환 생략 가능하나 생략하지 않고도 코드 작성이 가능합니다.
        gc = (GeneralCar) f1; 
        
        // down-casting이므로 형 변환을 생략할 수 없습니다.
        f2 = (FireFighterCar) gc;
        f2.water();      // Water!
    }
}

 

 

 

 

3-4. instanceof 연산자

- instanceof 연산자는 참조 변수가 실제로 참조하고 있는 객체(클래스의 인스턴스) 타입을 체크하는데 사용되는 연산자입니다.

 

- 문법 : 참조변수명 instanceof 연산할 클래스명

 

- 참조 변수의 형 변환 가능 여부를 체크할 때 주로 사용하며 instanceof 연산자의 연산 결과가 true이면 자신 클래스를 제외한 나머지 타입으로 형 변환이 가능합니다. (상속 계층도에서 해당 객체와 부모 ↔ 자식 클래스 관계이기에 가능)

 

 

- instanceof 연산자를 사용하여 실제 객체의 타입을 체크할 때, 해당 객체의 부모(또는 최고 조상) 클래스에 대해서도 모두 true를 반환합니다. (상속 계층도를 바탕으로 자신을 포함한 부모(조상) 클래스도 모두 True)

// 위에서 정의한 코드를 바탕으로 새로운 소방차 객체를 생성하였다.
FireFighterCar f1 = new FireFighterCar();

/* 아래 코드는 형 변환 전 해당 타입으로 형 변환이 가능한지 확인하기 위해 
instanceof 연산자를 사용하였다. */

/* FireFighter 자신 클래스를 제외하고 나머지는 모두 부모 클래스이므로 형 변환이 가능하기에
True가 반환되었다 */
System.out.println(f1 instanceof Object);     // true
System.out.println(f1 instanceof GeneralCar);     // true
System.out.println(f1 instanceof FireFighterCar);     // true

 

 

 

 

3-5. 매개변수(Parameter)의 다형성 - 다형성의 장점(1)

- 메서드의 매개변수로 객체 타입을 넘겨준다면, 해당 클래스의 객체뿐만이 아니라 해당 객체의 자식 클래스의 객체도 넘겨줄 수 있습니다.

 

- 메서드 호출 시, 매개변수로 자신과 같은 객체 타입 또는 해당 객체의 자식 클래스 객체 타입을 넘길 수 있으며 이를 통해 메서드의 호출 결과가 다양해질 수 있습니다.

 

 

 

 

3-6. 여러 종류의 객체를 배열로 다루는 방법 - 다형성의 장점(2)

- 하나의 배열에 여러 종류의 객체를 저장하여 관리할 수 있습니다.

 

- 즉, 부모 클래스 타입 배열에 자식 클래스의 인스턴스들을 담아서 관리할 수 있으며 이 부분은 객체지향의 다형성을 이용한 뚜렷한 장점입니다.

 

 

 

 

 

 

 

- 학부에서 수강했던 전공 수업 내용을 정리하는 포스팅입니다.

- 내용 중에서 오타 또는 잘못된 내용이 있을 시 지적해 주시기 바랍니다.

댓글