ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JAVA] 객체지향 2편 제어자 다형성
    JAVA 2022. 8. 2. 10:17
    728x90
    반응형

    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; // 배열에 하나씩 차곡차곡 자식들의 객체를 담는다.
        }
    }
    728x90
    반응형
Designed by Tistory.