[스터디]Java의 정석 12일차

Kristopher·2022년 1월 7일
0

Java 스터디

목록 보기
12/31

(Ch7) 4. 제어자 ~ 5.6 여러 종류의 객체를 배열로 다루기

제어자란 무엇인가?

제어자는 클래스, 변수 또는 메소드의 선언부에 함께 사용되어 부가적인 의미를 부여한다. 제어자의 종류는 크게 접근 제어자와 그 외로 나눌 수 있다. 제어자는 여러가지를 조합할 수 있으나 접근 제어자의 경우에는 4가지 중 한가지만을 사용해야 한다. 제어자는 제일 왼쪽에 위치한다.

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

그 외의 제어자 - static

앞선 예제에서 static을 자주 보았고 클래스 변수와 클래스 메소드를 지정할 때 사용하였던 제어자이다. static은 '공통적인'이라는 뜻을 갖고 있기에 클래스 변수의 경우 모든 인스턴스가 값을 공유하는 것을 확인하였다. 또한 클래스가 메모리에 올라갈 때 정의되므로 인스턴스를 생성하지 않아도 사용할 수 있다.

그 외의 제어자 - final

앞에서 상수를 정의할 때 final을 사용한 것을 보았다. 상수는 한번만 정의되고 그 값이 변경되지 않는 속성이 있다고 배웠는데, final을 붙인 클래스, 메소드, 변수 또한 이러한 속성을 가지게 된다. 클래스의 경우 확장도리 수 없는 클래스(즉, 조상 클래스가 될 수 없다), 메소드는 변경이 불가능하므로 오버라이딩이 불가능해지고, 변수의 경우 상수가 된다.

그 외의 제어자 - abstract

abstract는 '추상적인'이라는 뜻을 가지고 있다. 그렇기에 메소드의 선언부만을 작성하고 실행될 코드는 구현하지 않은 추상 메소드를 선언하는데 사용한다. abstract를 클래스에 붙일 경우 클래스 내부에 추상 메소드를 가지고 있다는 뜻으로 해석한다.

접근 제어자

접근 제어자의 경우 4가지 종류가 있다고 하였다. '접근'이라는 단어의 의미에 맞춰 접근 제어자는 어디에서 접근할 때 허용할 것인지에 대한 허용 범위를 규정짓는 제어자이다.

private : 같은 클래스 내에서만 접근 허용
default : 같은 패키지 내에서만 접근 허용
protected : 같은 패키지 내 + 다른 패키지의 자손 클래스에서 접근 허용
public : 접근에 제약이 없는 경우

각각의 접근 제어자가 어디에서 사용될 수 있는지 확인해보자.

클래스 : public, (default) 사용 가능
메소드/멤버변수 : public, protected, (default), private

접근제어자를 사용하는 이유는 클래스 내부에 선언된 데이터의 변형을 막기 위함이다. 데이터가 유효 범위 안에서 존재하도록, 외부에서 데이터 값을 임의로 변경하지 못하도록 하기 위해 접근 제어자를 사용한다. 이러한 방법을 데이터 감추기, 객체지향개념의 캡슐화라고 부른다. 또 다른 이유는 클래스 내부에서 임시적으로 사용하는 변수를 감추기 위해 사용한다. 외부에 불필요한 변수를 노출시키지 않으면 복잡성을 줄일 수 있기 때문이다.

생성자에 접근 제어자를 사용하면 인스턴스의 생성을 제한할 수 있다. 생성자의 접근 제어자를 private으로 설정하면 외부에서 인스턴스를 생성할 수 없다. 하지만 클래스 내부에서는 인스턴스를 생성할 수 있으므로, 클래스 내부에서 인스턴스를 생성하여 외부에서 해당 클래스의 인스턴스를 사용도록 할 수 있다.

//singleton(싱글톤) : 객체의 인스턴스가 1개만 생성되는 상황
class singleton {
    //getInstance에서 사용할 수 있도록 static 사용
    private static Singleton s = new Singleton();
    // 생성자에 private을 붙여 인스턴스 생성 제한
    private Singleton() {...}
    외부에서 호출할 수 있도록 public, 인스턴스 생성하지 않고 호출할 수 있도록 static
    public static Singleton getInstance(){
    	return s;
    }
}

제어자의 조합

각각의 제어자별로 다양한 의미를 갖고 있기 때문에 논리적으로 충돌하지 않게 제어자를 적절히 조합하여 사용하여야 한다.

  1. 메소드에 static과 abstract 함께 사용 금지
  • static은 실행할 코드가 있는 경우에만 사용할 수 있기 때문
  1. 클래스에 abstract와 final 함게 사용 금지
  • final을 붙이면 더 이상 수정이 불가한데 abstract는 상속을 통해서 완성될 수 있으므로 모순이 생긴다.
  1. abstract메소드의 접근 제어자가 private 금지
  • 2번과 유사하게 abstract의 경우 자손 클래스에서 구현되어야 하는데 private으로 설정하면 자손 클래스에서 접근할 수 없기 때문
  1. 메소드에 private과 final을 동시에 쓸 필요는 없다.
  • private인 메소드는 오버라이딩 될 수 없기 때문에 final을 붙이지 않아도 의미가 충분하다.

다형성(Polymorphism)

객체지향에서 다형성은 매우 중요한 개념이다. 다형성이란 '여러가지 형태를 가질 수 있는 능력'을 의미하는데 한가지 타입의 참조변수로 여러가지 타입의 객체를 참조할 수 있도록하여 다형성을 구현하였다. 지금까지 인스턴스를 생성할 때는 참조변수의 타입과 인스턴스의 타입이 동일하였다. 하지만 자바는 다형성을 갖고 있기 때문에 조상 클래스 타입의 참조변수로 자손 클래스 타입의 인스턴스를 참조할 수 있다.( 자손 클래스의 참조변수로 조상 클래스 타입의 인스턴스를 생성하는 것을 불가능)

// Tv 클래스가 조상 클래스, Caption클래스가 자손 클래스라 가정
Tv t = new CaptionTv();

지금까지 인스턴스를 생성하면 참조변수와 같은 타입이었기 때문에, 참조변수를 통해 인스턴스의 모든 멤버를 사용할 수 있었다. 하지만 조상 클래스의 참조변수로 자손 클래스의 인스턴스를 생성한 경우에는 자손 클래스 내부의 인스턴스 변수나 메소드는 사용할 수 없다.
또한 조상 클래스에서 선언도니 멤버변수와 같은 이름을 자손 클래스의 인스턴스 변수명으로 정의하면 조상타입의 참조변수로 자손 인스턴스를 참조하는 것과 자손 타입의 참조변수로 자손 인스턴스를 참조할 때 서로 다른 결과를 얻으므로 주의해야 한다.

참조변수의 형변환

기본형 변수와 같이 참조변수도 형변환이 이루어질 수 있다. 참조변수의 형변환의 경우 조상클래스와 자손클래스 사이에서만 일어날 수 있으며 두가지 종류가 있다.

자손타입 -> 조상타입 ( Up-casting ) : 형변환 생략가능
조상타입 -> 자손타입( Down-casting ) : 형변환 생략불가능

코드를 통해 이해해보자

// Car : 조상 클래스, FireEngine, Ambulance : 자손 클래라 가정
Car car = null;
FireEngine fe = new FireEngine();
FireEngine fe2 = null;
// FireEngine 타입인 fe를 Car 타입에 할당하기 위해 형변환 필요
car = fe; // Up-casting의 경우이므로 생략
// Car 타입의 car를 FireEngine 타입의 fe2에 할당하기 위해 형변환 필요
fe2 = (FireEngine)car;  // Down-casting

형변환이 발생하면 다형성에서의 조건을 위배할 수 있기 때문에 검증하는 과정이 필요하다. 이 때 사용할 수 있는 것이 instanceof 연산자이다.

// 참조변수 instanceof 타입(클래스명) 형태로 사용. 참이면 true, 아니면 false 반환
void doWork(Car c){
    if (c instanceof FireEngine){
        FireEngine fe = (FireEngine)c;
        fe.water();
        ...
    } else if ( c instanceof Ambulance){
        Ambulance a = (Ambulance)c;
        a.siren();
        ...
    }
}

매개변수의 다형성

매개변수의 자리에도 참조변수를 넣을 수 있다. 매개변수로 조상 클래스 타입의 참조변수를 설정하고 자손 클래스 타입의 참조변수를 넣어 메소드를 호출하면 자손 클래스의 변수 값을 각각 다르게 받을 수 있으므로 코드의 중복을 피할 수 있다.

여러 종류의 객체를 배열로 다루기

조상 클래스 타입의 참조변수로 자손 클래스 타입의 객체를 참조하는 것이 가능하므로 조상 클래스 타입의 참조변수를 배열로 처리하여 각 배열의 자리에 자손 클래스의 객체를 넣어 사용할 수 있다.

Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();

다만 배열을 사용할 경우에 배열의 길이를 설정하는 것에 대해 애매할 수 있는데, 이러한 경우에 Vector클래스를 이용하여 해결할 수 있다. Vector클래스는 10개의 객체를 생성할 수 있는 배열을 생성하고 10개 이상의 인스턴스가 저장되면 자동적으로 크기가 조절되는 성질을 가지고 있다.

Reference

Java의 정석
남궁성의 정석코딩

profile
개발자 지망생입니다.

1개의 댓글

comment-user-thumbnail
2022년 1월 8일

열심인 모습 보기 좋아요!

답글 달기