오늘의하루

[자바의 정석] 제어자 & 다형성 본문

JAVA

[자바의 정석] 제어자 & 다형성

오늘의하루_master 2022. 10. 18. 11:40

제어자(modifiers)

제어자는 클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여하며 크게 접근 제어자와 그 외의 제어자로 나눠집니다.

  • 하나의 대상에 여러 개의 제어자를 조합해서 사용할 수 있지만 접근제어자는 단 하나만 사용할 수 있다.
접근 제어자 : public, protected, default, private
그 외 제어자 : static, final, abstract, native, transient, synchronized, volatile, strictfp

그 외의 제어자 알아보기

static - 클래스의, 공통적인

static이 사용될 수 있는 곳은 멤버 변수, 메서드, 초기화 블록이다.

대상 의미
멤버변수 1. 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 된다.
2. 클래스 변수는 인스턴스 생성없이 접근이 가능하다.
3. 클래스가 메모리에 로드될 때 생성되며 프로그램 종료시 소멸한다.
메서드 1. 인스턴스 생성 없이 호출이 가능한 static 메서드가 된다.
2. static 메서드 내에서는 인스턴스 멤버들을 사용할 수 없다.
class StaticTest{
    static int width = 200; // static 변수
    static int height = 120; // static 변수
    
    static{ // static 초기화 블록
        // static 변수의 복잡한 초기화 수행
    }
    
    static int max(int a, int b){ // static 메서드
        return a > b ? a : b;
    }
}

final - 마지막의, 변경될 수 없는

fianl이 사용될 수 있는 곳은 클래스, 멤버 변수, 메서드, 지역변수이다.

  • 대표적인 final 클래스로는 String과 Math가 있다.
대상 의미
클래스 변경될 수 없는 클래스이며 확장 될 수 없는 클래스가 된다.
 - final로 지정된 클래스는 다른 클래스의 부모가 될 수 없다.
메서드 변경될 수 없는 메서드이며 오버라이딩을 할 수 없다.
멤버변수 변수 앞에 fianl이 붙으면 값을 변경할 수 없는 상수가 된다.
지역변수
final class FinalTest{
    final int MAX_SIZE = 10; // 멤버변수 (상수)
    
    final void getMaxSize(){
        final LV = MAX_SIZE; // 지역변수 (상수)
        return MAX_SIZE;
    }
}

class Child extends FinalTest{ // Error! 상속 받을 수 없다.
    void getMaxSize(){} // Error! 오버라이딩 할 수 없다.
}

생성자를 이용한 final 멤버 변수 초기화

final이 붙은 멤버 변수는 상수이며 보통은 선언과 초기화를 동시에 하지만 인스턴스 변수의 경우 생성자에서 초기화할 수 있다.

class Card{
    final int NUMBER;
    final String KIND;
    static int width = 100;
    static int height = 250;
    
    Card(String kind, int num){ // 생성자로 final멤버변수 초기화
        KIND = kind;
        NUMBER = num;
    }
    Card(){
        this("HEART", 1); // Card("HEART", 1) 호출
    }
    
    public String toString(){ // toString 오버라이딩
         return " " + KIND + " " + NUMBER:
    }
}

Card c = new Card();
c.NUMBER = 5; // Error! 상수는 변경할 수 없다.
System.out.println(c.KIND); // HEART
System.out.println(c.NUMBER); // 1

abstract - 추상의, 미완성의

abstract가 사용될 수 있는 곳은 클래스, 메서드이다.

대상 의미
클래스 클래스 내에 추상 메서드가 선언되어 있음을 의미한다.
메서드 선언부만 작성하고 구현부는 작성되지 않는 추상 메서드임을 알린다.
abstract class AbstractTest{ // 추상 클래스
    abstract void move(); // 추상 메서드
}

접근 제어자 알아보기

접근 제어자는 멤버 또는 클래스에 사용되어 외부로부터 접근을 제한합니다.

대상 의미
private 같은 클래스 내에서만 접근이 가능하다.
사용하는 곳 : 멤버
(default) 같은 패키지 내에서만 접근이 가능하다.
사용하는 곳 : 클래스, 멤버
protected 같은 패키지 내에서, 그리고 다른 패키지의 자식 클래스에서 접근이 가능하다.
사용하는 곳 : 멤버
public 접근 제한이 없다.
사용하는 곳 : 클래스, 멤버

접근 제어자를 이용한 캡슐화

캡슐화란 외부로부터 불필요한 접근으로부터 데이터를 보호하는 방법을 말한다.

class Time{
    private int hour;
    private int minute;
    private int second;
    
    Time(int hour, int minute, int second){
        
    }
    
    public int getHour(){return hour;}
    public int getMinute(){return minute;}
    public int getSecond(){return second;}
    
    public void setHour(int hour){
        if(hour < 0 || hour > 23){
            return;
        }
        this.hour = hour;
    }
    public void setMinute(int minute){
        if(minute < 0 || minute > 59){
            return;
        }
        this.minute = minute;
    }
    public void setSecond(int second){
        if(second < 0 || second > 59){
            return;
        }
        this.second = second;
    }
    
    public String toString(){
        return hour + ":" + minute + ":" + second;
    }
}

// ======================================================

Time t = new Time(12,35,10);
System.out.println(t); // == System.out.println(t.toString());
t.hour = 5; // Error! 멤버변수의 접근제어자가 private이기 때문에 직접 접근할 수 없다.
t.setHour(5); // OK

System.out.println(t)는 System.out.println(t.toString())과 같으며 toString 메서드는 생략이 가능하다.

생성자의 접근 제어자

일반적으로 생성자의 접근 제어자는 클래스의 접근제어자와 일치하며 생성자에 접근 제어자를 사용함으로써 인스턴스의 생성을 제한할 수 있다.

final class Singleton{
    private static Singleton s;
    
    private Singleton(){ // 생성자
        // ....
    }
    
    public static Singleton getInstance(){
        if(s == null){
            s = new Singleton();
        }
        return s;
    }
}

Singleton s1 = new Singleton(); // Error! 생성자의 접근제어자가 private이기 때문에 접근 불가
Singleton s2 = Singleton.getInstance(); // OK
// getInstance는 static메서드 이기 때문에 인스턴스 생성없이 호출 가능
// getInstance 메서드 안에서 생성자 호출이 가능하다.

제어자의 조합

대상 사용 가능한 제어자
클래스 public, (default), final, abstract
메서드 모든 접근 제어자, final, abstract, static
멤버변수 모든 접근 제어자, final, static
지역변수 final
  1. 메서드에 static과 abstract를 함께 사용할 수 없다.
    • static 메서드는 구현부가 있는 메서드에만 사용할 수 있기 때문이다.
  2. 클래스에 abstract와 final을 동시에 사용할 수 없다.
    • fianl 클래스는 부모가 될 수 없는데 abstract는 자식 클래스가 상속을 받아 추상 메서드의 구현부를 완성해야 하기 때문이다.
  3. abstract메서드의 접근제어자가 private일 수 없다.
    • abstract메서드는 자식 클래스에서 구현해 주어야 하지만 접근 제어자가 private일 경우 자식 클래스에서 접근이 불가능하여 구현부를 완성할 수 없기 때문입니다.
  4. 메서드에 private과 final을 같이  사용할 필요는 없다.
    • 접근 제어자가 private인 메서드와 fianl인 메서드 모두 오버 라이딩을 할 수 없다는 의미이기 때문에 중복된다.

다형성(polymorphism)

다형성은 하나의 참조 변수로 여러 타입의 객체를 참조할 수 있는 것이며 부모 타입의 참조 변수로 자식 타입의 객체를 다룰 수 있는 것이다.

class Tv{
    boolean power;
    int channel;
    
    void power(){power = !power;}
    void channelUp(){++channel;}
    void channelDown(){--channel;}
}

class CaptionTv extends Tv{
    String text;
    void caption(){/* 내용 생략 */}
}

Tv t = new CaptionTv();
// 부모 타입의 참조변수 t는 자식 타입의 객체 CaptionTv를 다룰 수 있다.

참조 변수의 형 변환

서로 상속관계에 있는 타입 간의 형 변환이 가능하다.

자식 타입에서 부모 타입으로 형 변환은 Up Casting (형 변환 생략 가능)
부모 타입에서 자식 타입으로 형 변환은 Down Casting (형 변환 생략 불가능)
class Car{
    String color;
    int door;
    
    void drive(){
        System.out.println("drive Brrrr");
    }
    void stop(){
        System.out.println("stop!");
    }
}

class FireEngine extends Car{
    void water(){
        System.out.println("water");
    }
}

class Ambulance extends Car{
    void siren(){
        System.out.println("siren");
    }
}

Car c = null;
FireEngine fe1 = new FireEngine();
FireEngine fe2 = null;

fe1.water(); // OK
c = fe1; // c = (Car)fe1; 자식타입 FireEngine에서 부모타입 Car로 형변환
c.water(); // Error!
fe2 = (FireEngine)c; // 부모타입 Car에서 자식타입 FireEngine으로 형변환
fe2.water(); // OK

instanceof 연산자

참조 변수가 참조하는 인스턴스의 실제 타입을 체크하는 데 사용하며 instanceof의 연산 결과가 true이면 해당 타입으로 형 변환이 가능하다.

  • A instanceof B는 A가 가리키는 실제 객체가 B 또는 B의 자식 타입인지 확인하는 것이다.
class InstanceofTest{
    FireEngine fe = new FireEngine();
    
    if(fe instanceof FireEngine){
        System.out.println("This is a FireEngine instance");
    }
    if(fe instanceof Car){
        System.out.println("This is a Car instance");
    }
    if(fe instanceof Object){
        System.out.println("This is a Object instance");
    }
}
/*
결과
This is a FireEngine instance
This is a Car instance
This is a Object instance
*/

참조 변수와 인스턴스 변수의 연결

  • 멤버 변수가 중복 정의된 경우 참조 변수의 타입에 따라 연결되는 멤버 변수가 달라진다.
  • 메서드가 중복 정의된 경우 참조 변수와 상관없이 실제 인스턴스에 정의된 메서드가 호출된다.
class Parent{
    int x = 100;
    
    void method(){
        System.out.println("This is Parent Method");
    }
}

class Child extends Parent{
    int x = 200;
    
    void method(){ // 오버라이딩
        System.out.println("This is Child Method"); 
    }
}

Parent p = new Child(); // 다형성 - 부모타입의 참조변수로 자식타입의 객체를 다룰 수 있다.
Child c = new Child();

System.out.println(p.x); // 100 (멤버변수는 참조변수 타입에 영향을 받는다.)
p.method(); // This is Child Method
System.out.println(c.x); // 200
c.method(); // This is Child Method

매개변수의 다형성

참조형 매개변수는 메서드 호출 시 자신과 같은 타입 또는 자식 타입의 인스턴스를 넘겨줄 수 있다.

class Product{
    int price;
}

class Tv extends Product{}
class Computer extends Product{}
class Audio extends Product{}

class Buyer{
    int money = 1000;
    
    void buy(Product p){ // 매개변수의 다형성
        money = money - p.price;
    }
    // 매개변수의 다형성을 사용하지 않을 경우
    //   - 제품이 많아 질수록 계속 만들어줘야 하는 불편함이 있다.
    void buy(Tv t){/* 내용 생략 */} // 오버로딩
    void buy(Computer c){/* 내용 생략 */} // 오버로딩
    void buy(Audio a){/* 내용 생략 */} // 오버로딩
}

Buyer b = new Buyer();
Product p1 = new Tv();
Product p2 = new Computer();
Product p3 = new Audio();
b.buy(p1);
b.buy(p2);
b.buy(p3);

여러 객체를 하나의 배열로 다루기

부모 타입의 배열을 만들면 각 요소에 자식의 객체를 담은 수 있다.

Product p1 = new Tv();
Product p2 = new Computer();
Product p3 = new Audio();

// 위에 코드를 배열로 만들어보자

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