6) 객체지향 프로그래밍5 - 캡슐화

dev-mage·2022년 10월 15일
0

Hello Java World!

목록 보기
15/32
post-thumbnail

Java의 패키지와 제어자를 통한 캡슐화

패키지(package)

패키지란 클래스나 인터페이스의 묶음을 뜻하며 서로 관련된 클래스끼리 그룹 단위로 묶어 패키지에 포함시킴으로써 효율적으로 관리할 수 있게 한다. 지금까지는 단순히 클래스 이름으로만 클래스를 구분했지만 사실 실제 이름은 클래스가 속한 모든 패키지명까지 포함한다. 이를 FQCN(Fully Qualified Class Name이라고 한다. 예를 들어 String 클래스의 FQCN은 java.lang.String이다.

클래스가 물리적으로 하나의 클래스파일(.class)인 것처럼 패키지는 물리적으로 하나의 디렉토리이다. 그래서 같은 이름의 클래스일지라도 서로 다른 패키지에 속하면 패키지명으로 구별이 가능하다.

클래스나 인터페이스의 소스파일(.java)의 최상단에 package 패키지명;으로 명시함으로써 패키지를 선언할 수 있다. 패키지 선언문은 반드시 소스파일에서 주석과 공백을 제외한 첫 번째 문장이어야 하고 하나의 소스파일에 단 한 번만 선언될 수 있으며 해당 소스파일에 포함된 모든 클래스나 인터페이스는 선언된 패키지에 속하게 된다. 패키지명은 대소문자 모두 허용하지만 원칙적으로 소문자로 하고 있다.

소스파일에 자신이 속할 패키지를 지정하지 않은 클래스는 자동적으로 ‘이름 없는 패키지(unnamed package)’에 속하게 되므로 따로 패키지를 선언하지 않아도 문제가 생기지는 않는다.

import문

소스코드를 작성할 때 다른 패키지의 클래스를 사용하려면 FQCN을 사용해야 한다. 하지만 매번 패키지명을 붙여서 작성하기란 매우 불편한 일이다. 이를 해결하기 위해서 import문을 써준다. import문은 컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보를 제공하게 한다. 컴파일 시에 컴파일러는 import문을 통해 소스파일에 사용된 클래스들의 패키지를 알아 낸 다음 모든 클래스명 앞에 패키지명을 붙여 준다. 따라서 소스코드 작성 시 클래스명에서 패키지명을 생략할 수 있다.

java.util.Date today = new java.util.Date();

///////////////////////////////////////////////////////////////

import java.util.Date;
.
.
.
	Date today = new Date();

모든 소스파일(.java)에서 import문은 package문 다음에, 그리고 클래스 선언문 이전에 위치해야 하며 여러 번 선언할 수 있다. import문은 import 패키지명.클래스명; 또는 import 패키지명.*; 과 같이 선언할 수 있다. * 는 프로그래밍에서 ‘모든’을 뜻하며 여기서는 패키지에 속하는 모든 클래스를 사용하겠다는 뜻을 나타낸다.

  • static import문 static 멤버를 호출할 때 클래스명을 생략하고 싶다면 static import문을 사용하면 된다.
    double pi = Math.PI;
    double d = Math.random()
    
    ///////////////////////////////////////////////////////////////
    
    import static java.lang.Math.*;
    
    double pi = PI;
    double d = random();

java.lang 패키지

다른 패키지의 클래스를 사용하려면 분명 FQCN이나 import문을 써야 한다고 했다. 그런데 생각해보면 우린 String 클래스나 System 클래스를 사용하면서 한번도 그런 적이 없다. 바로 이 두 가지가 모두 java.lang이라는 패키지에 속하기 때문인데 java.lang은 모든 클래스에서 암시적으로 선언되는 패키지이다.

제어자(modifier)

modifier는 수식어라는 뜻을 가지고 있다. 자바에서는 이를 제어자라 부르며 클래스, 변수 또는 메서드의 선언부에 부가적인 의미를 부여하는데 사용한다. 영어 뜻대로 이들을 수식하는 것이다. 제어자의 종류는 크게 접근 제어자와 그 외의 제어자로 나눌 수 있다.

  • 접근 제어자: public, protected, default, private
  • 그 외 제어자: static, final, abstract, native, transient, synchronized, volatile, strictfp

제어자들 간에 순서는 없지만 주로 접근 제어자를 제일 앞에 명시한다.

접근 제어자(access modifier)

접근 제어자는 클래스 또는 멤버에 사용되어 해당하는 클래스 또는 멤버를 외부에서 접근하지 못하도록 제한한다. 기본 접근 제어자는 default로 접근 제어자가 명시되어 있지 않으면 모두 default 상태이다.

  • 사용될 수 있는 곳: 클래스, 멤버 변수(클래스 변수, 인스턴스 변수), 메서드, 생성자
  • 접근 범위: public > protected > (default) > private
구분publicprotected(default)private
같은 클래스
같은 패지키의 자식 클래스
같은 패키지
다른 패키지의 자식 클래스
다른 패키지
  • protected는 패키지에 관계없이 상속 받은 자식 클래스라면 접근할 수 있다.

정보 은닉(information hiding)과 캡슐화(encapsulation)

접근 제어자를 사용하는 이유는 클래스 내부에 선언된 데이터를 보호하고 감추기 위해서이다.

예를 들어 커피를 나타내기 위한 Coffee 클래스가 있고 커피의 가격은 최소 3000원부터, 샷을 추가하면 추가 금액을 받도록 하려한다. 하지만 제한되지 않은 멤버 변수는 해당 클래스의 인스턴스를 생성한 다음, 멤버 변수에 직접 접근하여 다음과 같이 값을 변경할 수 있다.

public class Coffee{
    int price;
    int extraShot;
    int calculatePrice() {
        return price + (extraShot * 500);
    }
}

/////////////////////////////////////////////////////////////////////

Coffee coffee = new Coffee();
coffee.price = 100;
coffee.extraShot = -1;
coffee.calculatePrice() // -400원이 됨.

위의 코드처럼 잘못된 값을 지정한다고 해도 이것을 막을 수 없다. 이런 경우 멤버 변수를 private이나 protected로 설정하고 멤버 변수의 값을 읽고 변경할 수 있는 public메서드를 제공함으로써 간접적으로 멤버 변수의 값을 다룰 수 있도록 하는 것이 바람직하다.

public class Coffee{
    private int price;
    private int extraShot;

    public void setPrice(int price) {
        if(price < 3000) price = 3000;
        this.price = price;
    }
		public void setExtraShot(int extraShot) {
        if(extraShot < 0) extraShot = 0;
        this.extraShot = extraShot;
    }
    public int getPrice() { return price; }
    public int getExtraShot() { return extraShot; }
		...
}

일반적으로 멤버 변수의 값을 읽는 메서드는 get멤버변수명()으로, 멤버 변수의 값을 변경하는 메서드는 set멤버변수명()으로 명명하고 각각을 getter / setter 라고 부른다.

이렇게 접근 제어자를 활용하면 데이터가 유효한 값을 유지하도록 하거나 데이터를 외부에서 함부로 변경하지 못하도록 할 수 있다. 또한 클래스 내에서 내부 작업을 위해 임시로 사용되는, 즉 외부에 공개할 필요가 없는 멤버를 감출 수도 있는데 이를 정보 은닉이라고 한다. 정보 은닉은 객체지향 프로그래밍의 기본 원리에 해당하는 캡슐화의 한 예로 캡슐화는 객체의 상태(변수)와 기능(메서드)을 묶어서 제공하는 것을 의미한다. 자바에서는 변수와 메서드를 하나로 묶어 클래스화함으로써 캡슐화를 구현한다. 캡슐화의 주요 목적은 외부에서 임의로 객체의 상태를 조작하는 작업을 방지하는 것인데 정보 은닉을 통해 그런 일로부터 객체를 보호할 수 있다.

static

static의 사전적 의미는 ‘고정된’이다. 자바에서 static으로 선언된 멤버는 Run-Time Data Areas의 Method Area라는 메모리 영역에 생성된다. 이 메모리 영역은 JVM이 동작하기 시작할 때 생성되며 JVM의 모든 스레드에서 공유된다. 그러니까 처음부터 공유될 수 있도록 ‘고정된’ 것이다. static 멤버는 모든 인스턴스가 공통적으로 사용해야 하기 때문에 인스턴스를 생성하지 않고 바로 클래스명.static멤버명으로 접근해서 사용한다. 그렇기에 static 멤버를 클래스 멤버라고도 부르는 것이다.

한 가지 주의할 점은 인스턴스 메서드는 static 멤버에 접근할 수 있지만 static 메서드는 인스턴스 멤버에 접근할 수 없다. 앞서 언급했다싶이 static 멤버는 JVM 동작이 시작되면 생성된다. 하지만 인스턴스 멤버는 인스턴스를 생성해야만 접근할 수 있으므로 static 멤버와의 생성 시기가 맞지 않는다. static 메서드에서 인스턴스 멤버를 사용하려고 하면 Non-static field '...' cannot be referenced from a static context 라는 에러가 발생하는 것을 확인할 수 있다.

  • 사용될 수 있는 곳: 멤버 변수, 메서드 및 초기화 블럭
public class StaticMembers {
		// 클래스 변수(static 변수)
		static int year = 2022;
		
		// 클래스 초기화 블럭(static 초기화 블럭)
		static {
				// 클래스 변수의 복잡한 초기화 작업
				...
		}

		// 클래스 메서드(static 메서드)
		public static void main(String[] args) {
        ...
    }
}

final

final은 ‘마지막의’라는 의미로 변경될 수 없는 대상에 사용된다.

  • 사용될 수 있는 곳: 클래스, 메서드, 멤버 변수, 지역 변수
  • final 클래스: 다른 클래스에서 상속할 수 없음.
    • 대표적인 final 클래스인 String

  • final 메서드: 오버라이딩을 통해 재정의 될 수 없음.
  • final 변수: 값을 변경할 수 없는 상수가 됨.

abstract

abstract은 ‘추상적인’이라는 의미로 메서드 선언부만 작성하고 실제 수행 내용은 구현하지 않은 추상 메서드를 선언하는데 사용된다. 그리고 추상 클래스에 사용되어 클래스 내에 추상 메서드가 존재한다는 것을 쉽게 알 수 있게 한다. 추상 클래스는 아직 완성되지 않은 메서드가 존재하는 ‘미완성 설계도’이므로 인스턴스를 생성할 수 없다.

  • 사용될 수 있는 곳: 클래스, 메서드
  • abstract 클래스: 클래스 내에 추상 메서드가 있음.
  • abstract 메서드: 선언부만 있고 구현부가 없음.

public static void main(String[] args){}

메서드와 접근 제어자에 대해서 알았다면 이제 자바의 main 메서드에 대해 파헤쳐 볼 수 있다. 자바 애플리케이션은 모두 public static void main(String[] args)를 메서드 시그니처로 가지는 main 메서드를 포함하고 있어야 한다. main 메서드는 프로그램의 진입점(entry point)으로써 main 메서드를 가지고 있는 클래스를 JVM이 애플리케이션 실행 시 찾아 제일 먼저 실행시킨다. main 메서드에서 더이상 수행할 동작이 없는 경우 프로그램도 종료된다. 따라서 JVM이 접근할 수 있도록 접근에 제한이 없는 public을, 인스턴스를 생성하지 않고 가장 먼저 접근할 수 있는 static을, 메서드가 끝나면 프로그램이 종료되므로 반환 값이 없는 void를 메서드 시그니처로 정한 것이다. String[] args는 명령행 인자(command-line argument)로써 콘솔에서 프로그램을 실행하는 경우 넘겨줄 인자를 지정할 수 있다.


References

0개의 댓글