오늘의하루

[JAVA] 객체지향 2편 제어자 다형성 본문

JAVA

[JAVA] 객체지향 2편 제어자 다형성

오늘의하루_master 2022. 8. 2. 10:17

1. 제어자

  • 클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여한다.
  • 제어자는 크게 접근 제어자와 그 외 제어자로 나뉜다.
    • 접근 제어자 : public, protected, default, private
    • 그 외 제어자 : static, final, abstract, native, transient, synchronized, volatile, strictfp
  • 하나의 대상에 여러 개의 제어자를 조합해서 사용할 수 있지만 접근 제어자는 단 하나만 사용이 가능하다.

1-1. 그 외 제어자에 대해 알아보기

static - 공통적인

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

  • 멤버 변수
    • 모든 인스턴스에 공통적으로 사용되는 클래스 변수가 된다.
    • 클래스 변수는 인스턴스를 생성하지 않고도 사용이 가능하다.
    • 클래스가 메모리에 로드될 때 생성된다.
  • 메서드
    • 인스턴스를 생성하지 않고도 호출이 가능한 static 메서드가 된다.
    • static 메서드 내에서는 인스턴스 변수를 사용할 수 없다.
class T{
    // 클래스 변수
    static int width = 200;
    static int height = 100;
    
    // 클래스 변수 초기화 블럭(복잡)
    static {} 
    
    // static 메서드
    static int max(int a, int b){ // 인스턴스 변수 사용 x
        return a > b ? a : b;
    }
}

final - 변경될 수 없는

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

  • 클래스
    • 다른 클래스의 부모가 될 수 없다. (확장이 불가능하다.)
  • 메서드
    • 오버 라이딩을 통해 재 정의될 수 없다.
  • 멤버 변수, 지역 변수
    • 값을 변경할 수 없는 상수가 된다.
// 상속 불가 메서드
final class Test{
    // 상수
    final int iv = 10;
    
    // 오버라이딩 불가 메서드
    final void get(){
        final lv = iv;
    }
}

class T extends Test{ // 💥Error > final class는 상속할 수 없다.
    void get(){}; // 💥 Error > final 메서드는 오버라이딩 할 수 없다.
}

final 멤버 변수 초기화

final이 붙은 변수는 상수이므로 보통 선언과 초기화를 동시에 하지만 인스턴스 변수의 경우 생성자를 통해 단 한 번만 초기화할 수 있다.

class card{
    final int num;
    final String kind;
    static int width = 300;
    
    // 생성자를 통해 final 멤버 변수 초기화 (단 한번)
    card(int num, String kind){
        this.num = num;
        this.kind = kind;
    }
    
    card(){
        this("HEART",1);
    }
}

public static void main(String[] args){
    card a = new card("HEART", 10);
    
   // 💥 Error : 상수는 선언과 동시에 초기화 하던지 생성자를 통해 단한번 초기화 해야한다.
    a.num = 4; 
}

abstract - 미완성의

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

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

1-2. 접근 제어자에 대해 알아보기

접근 제어자는 외부로부터 접근을 제한하여 데이터를 보호하며 내부적으로만 사용되는 부분을 감추기 위해 사용된다.

  • public : 접근 제한이 없다.
    • 클래스, 멤버 변수, 메서드에서 사용이 가능하다.
  • protected : 같은 패키지 및 다른 패키지의 자식 클래스에서만 접근이 가능하다.
    • 클래스에 사용이 불가능하다.
    • 멤버 변수, 메서드에서 사용이 가능하다.
  • default : 같은 패키지에서만 접근이 가능하다.
    • 클래스, 멤버 변수, 메서드에서 사용이 가능하다.
    • default의 경우 생략이 가능하다.
  • private : 같은 클래스 내에서만 접근이 가능하다.
    • 클래스에 사용이 불가능하다.
    • 멤버 변수, 메서드에서 사용이 가능하다.
(default)class Time{
    private int hour;
    
    Time(int hour, int minute, int second){
        //super();
        this.hour = hour;
    }
    
    public int getHour(){return hour;}
    public void setHour(int hour){
        if (hour < 0 || hour > 23){
            return;
        }
        this.hour = hour;
    }
}

public static void main(String[] args){
    Time test = new Time(10);
    
    // 💥 Error : private 변수는 외부 클래스에서 접근이 불가능하다.
    System.out.println(test.hour); 
    test.hour = 3;
    
    // private 변수를 사용하고 싶다면 메서드를 통해 접근해야한다.
    test.setHour(22); // private 변수 hour의 값을 변경한다.
    System.out.println(test.getHour()); // private 변수 hour을 보여준다.
}

제어자 총 종리

  • 클래스
    • public, default, final, abstract
  • 메서드
    • 모든 접근 제어자, final, abstract, static
  • 멤버 변수
    • 모든 접근 제어자, final, static
  • 지역 변수
    • final

Q. 메서드에 static과 abstract를 같이 사용할 수 없는 이유는?

A. static 메서드는 구현부가 있는 메서드만 사용할 수 있기 때문이다.

 

Q. 클래스에 abstract와 final을 같이 사용할 수 없는 이유는?

A. abstract는 상속을 통해 완성돼야 하기 때문에 상속을 하지 못하는 final을 같이 사용할 수 없다.

 

Q. abstract 메서드의 접근제어자로 private을 사용할 수 없는 이유는?

A. abstract 메서드는 상속을 통해 완성돼야 하는데 private일 경우 자식 클래스에서 접근할 수 없기 때문이다.

 

Q. 메서드에 private과 final을 같이 사용할 수 없는 이유는?

A. private 메서드는 외부에서 접근을 못하기 때문에 오버 라이딩을 할 수 없고 final 메서드는 오버 라이딩을 하지 못하게 만들기 때문에 의미가 중복된다. 


2. 다형성

부모 타입의 참조 변수로 자식 타입의 객체를 다룰 수 있는 것을 다형성이라고 부른다.

다형성의 장점

  • 하나의 참조 변수로 여러 타입의 객체를 참조할 수 있다.
  • 여러 가지 형태를 가질 수 있다.

다형성을 사용할 때 기억해두어야 할 점은 부모 타입의 참조 변수는 자식 타입의 인스턴스를 참조할 수 있지만 반대의 경우는 성립되지 않는다.

(default)class Tv{
   int channel;
   
   Tv(){
       this(1);
   }
   Tv(int x){
       //super();
       this.channel = x;
   }
   
   void channelUp(){
       ++channel;
   }
}

class SmartTv extends Tv{
    String text;
    
    SmartTv(){
        this(1,"Hello Java");
    }
    SmartTv(int x, String text){
        super(x);
        this.text = text;
    }
    
    void caption(){
        System.out.println(text);
    }
}

public static void main(String[] args){
    SmartTv S = new SmartTv(); // 총 멤버 : 4 | 사용 가능 멤버 : 4
    Tv T = new SmartTv(); // 총 멤버 : 4 | 사용 가능 멤버 : 2
    
    // 💥 Error : Tv 타입의 참조변수 T는 caption() 메서드를 가지고 있지 않다.
    T.caption(); 
    // 💥 Error : Tv 타입의 참조변수 T는 String text 인스턴스 변수를 가지고 있지 않다.
    T.text = "hi~";
}

2-1. 참조 변수의 형 변환

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

class car{
    String color;
    
    void drive(){
        System.out.println("drive~~");
    }
    void stop(){
        System.out.println("stop");
    }
}

class fireEngine extends car{
    void water(){
        System.out.println("water~");
    }
}

public static void main(String[] args){
    fireEngine f = new fireEngine();
    // 참조변수 f에 fireEngine 인스턴스의 주소 저장
    car c = null;
    // 참조변수 c에 null을 저장
    
    c = (car)f;
    // car타입의 참조변수 c에 fireEngine 인스턴스 주소를 저장
    // 두개의 참조변수 타입이 같아야 하므로 참조변수 f의 타입을 car타입으로 형변환 시켜준다.
    // car타입에는 변수 color, 메소드 drive, stop 있다.
    // 생성된 인스턴스에는 메서드 water가 있지만 사용할 수 없다.
    c.water(); // 💥 Error
    
    fireEngine f2 = null;
    // 참조변수 f2에 null을 저장
    f2 = (fireEngine)c;
    // fireEngine타입의 참조변수 f2에 fireEngine 인스턴스 주소를 저장
    // 참조변수 c의 타입은 car이기 때문에 fireEngine으로 형변환 시켜준다.
    // fireEngine 타입에는 모든 멤버가 있기 때문에 모두 사용 가능하다.
    f2.water(); // ⭐ 정상작동
}

2-2. instanceof 연산자

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

A instanceof B 연산자를 읽는 방법은 A의 부모 타입에 B가 있다면 true 아니라면 false를 반환합니다.

class car {
    void drive(){
        System.out.println("drive");
    }
}
class fire extends car{
    void water(){
        System.out.println("watre~");
    }
}

public static void main(String args[]) {
    fire a = new fire();
    car c = new car();
    
    if(a instanceof fire){
        System.out.println("This is a fire instance");
    }
    if(a instanceof car){
        System.out.println("This is a car instance");
    }
    if(a instanceof Object){
        System.out.println("This is a Object instance");
    }
    
    if(c instanceof fire){
        System.out.println("car의 부모타입에는 fire가 없으므로 실행 x");
    }
}
// 결과
// This is a fire instance
// This is a car instance
// This is a Object instance

2-3. 참조 변수와 인스턴스 변수의 연결

  • 멤버 변수가 중복 정의된 경우 참조 변수의 타입에 따라 연결되는 멤버 변수가 달라진다.
    • 참조 변수 타입에 영향을 받는다.
  • 메서드가 중복 정의된 경우 참조 변수의 타입에 관계없이 항상 실제 인스턴스의 타입에 정의된 메서드가 호출된다.
    • 참조 변수 타입에 영향을 받지 않는다.
public static void main(String args[]) {
    Parent p = new Child();
    Child c = new Child();
        
    System.out.println("p.x = " + p.x); // p.x = 10
    p.method(); // Child method
      
    System.out.println("c.x = " + c.x); // c.x = 20
    c.method(); // Child method
      
    Parent2 p2 = new Child2();
    Child2 c2 = new Child2();
        
    System.out.println("p2.x = " + p2.x); // p2.x = 30
    p2.method(); // Parent2 method
        
    System.out.println("c2.x = " + c2.x); // c2.x = 30
    c2.method(); // Parent2 method
}


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

class Parent2{
    int x = 30;
    void method(){
        System.out.println("Parent2 method");
    }
}
class Child2 extends Parent2{ }

2-4. 매개변수의 다형성

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

public class MyClass {
    public static void main(String args[]) {
        Product tv = new Tv(10);
        Product audio = new Audio(5);
        
        Buyer buyer = new Buyer();
        buyer.buy(tv);
        System.out.println(buyer.money);
    }
}

class Product {
    int price;
    Product(int x){
        price = x;
    }
}
class Tv extends Product{
    Tv(int x){
        super(x);
    }
}
class Audio extends Product{
    Audio(int x){
        super(x);
    }
}

class Buyer{
    int money;
    
    Buyer(){this(1000);}
    Buyer(int x){
        money = x;
    }
    
    // 매개변수의 다형성 사용 O
    // 물건이 추가되더라도 이 코드는 수정하지 않아도 된다.
    void buy(Product p){ 
        money = money - p.price;
    }
    
    // 매개변수의 다형성 사용 x
    // 물건이 추가되면 이 코드를 수정해줘야 한다.
    void buy2(Tv t){
        money = money - t.price;
    }
    void buy2(Audio a){
        money = money - a.price;
    }
}

2-5. 여러 종류의 객체를 하나의 배열로 다루기

원래 배열은 같은 타입만 가능하지만 다형성을 이용하면 부모 타입의 배열에 각기 다른 자식 타입들의 인스턴스를 담을 수 있습니다.

import java.util.*;

public class MyClass {
    public static void main(String args[]) {
        // 다형성을 이용해서 자식 타입의 객체를 생성후 부모 타입의 참조변수에 주소를 저장시킨다.
        Product tv = new Tv(10);
        Product audio = new Audio(5);
        
        Buyer buyer = new Buyer();
        buyer.buy(tv); // tv 구매
        buyer.buy(tv); // tv 구매
        buyer.buy(audio); // audio 구매
        System.out.println(buyer.money); // buyer 남은 돈 확인
        System.out.println(Arrays.toString(buyer.cart)); // 구매 물품 배열로 확인
        System.out.println(buyer.cart[0].price); // 배열의 0번째에 들어있는 물건 가격확인
        System.out.println(buyer.sum); // 배열에 있는 모든 가격 합산
    }
}

class Product {
    int price;
    Product(int x){
        price = x;
    }
}
class Tv extends Product{
    Tv(int x){
        super(x);
    }
}
class Audio extends Product{
    Audio(int x){
        super(x);
    }
}

class Buyer{
    int money;
    Product[] cart = new Product[10]; // 부모타입에 배열 생성
    int i = 0;
    int sum = 0;
    
    Buyer(){this(1000);}
    Buyer(int x){
        money = x;
    }
    
    void buy(Product p){ 
        money = money - p.price;
        sum = sum + p.price;
        cart[i++] = p; // 배열에 하나씩 차곡차곡 자식들의 객체를 담는다.
    }
}
Comments