본문 바로가기

Etc../SWExpertAcademy

[Java Programming] 객체지향 핵심 원리(6)

추상 클래스와 객체의 형변환

■ 추상 클래스와 내부 클래스

추상메서드

  • 메서드의 시그니쳐( 리턴타입, 메서드명, 매개변수 )만 정의
  • 구체적인 행위는 정의되지 않은 특수한 메서드
  • abstract 예약어를 사용
  • 상속을 통해 사용되는 클래스마다 필수로 가져야하는 메서드의 형태를 정의
  • 추상 메서드를 Overriding하지 않으면 컴파일 불가능

추상 클래스

  • 추상 클래스는 일반적으로 하나 이상의 추상 메서드를 포함
  • 추상 메서드를 포함하고 있으면 무조건 추상 클래스로 선언해야함.
  • 추상 클래스는 객체 생성을 할 수 없음
  • 추상 클래스를 상속받은 클래스는 추상 메서드를 OVerriding해야 객체를 생성할 수 있음
  • 유지보수의 편의성을 높이기 위해 사용함. (상속을 통해 동일한 기능을 포함한 클래스가 됨)

내부 클래스

  • 클래스가 다른 클래스를 포함하는 경우 내부에 포함된 클래스를 내부 클래스라고 함
  • 정의되는 위치에 따라 멤버 클래스와 지역 클래스로 나뉨

멤버클래스
  • 멤버 변수와 동일한 위치에 선언된 내부 클래스
  • static예약어가 붙은 static멤버와 instance멤버로 나뉨
  • 동일한 클래스 뿐만 아니라 다른 클래스에서도 활용 가능
  • 클래스의 멤버 변수와 성격이 비슷

##### instance 멤버 내부 클래스 * 클래스의 멤버와 동일한 위치에서 선언되는 내부 클래스 * 다른 외부의 클래스에서도 사용 가능
class Outside{            // 외부 클래스 (Top Level 클래스라고도 함)

    public class Inside{    // instance 멤버 내부 클래스
    }
}
public class InnerClassTest{
    public static void main(String[] args){
        Outside outer = new Outside();        // 내부 클래스의 객체 생성을 위해 외부 클래스의 객체를
        Outside.Inside inner = outer.newInside();    // 생성해야 함.
    }
}

static 멤버 내부 클래스
  • 외부 클래스 객체를 생성하지 않고도 내부 클래스 객체를 생성할 수 있음
class Outside{            

    public static class StaticInner{    // static 예약어를 이용하여 정의
    }
}
public class InnerClassTest{
    public static void main(String[] args){
        Outside.StaticInner sin = new Outside.StaticInner();
    }                // static 내부 클래스 객체를 생성할 때 외부 클래스 객체를 생성하지 않아도 됨.
}


지역 클래스
  • 메서드 내에 클래스가 정의되어 있는 경우
  • 지역 클래스( 이름 있음 )와 무명 클래스( 이름 없음 )로 나뉨
  • 활용 범위가 메서드 블록 내부로 제한
  • 지역 변수와 성격이 비슷함
  • 자바의 클래스 구조를 더 조직화, 소스코드를 구현 시 효율을 높일 수 있음.

이름이 있는 지역 내부 클래스
  • 메서드 내부에서 정의된 클래스, 지역 변수와 동일한 범위를 가짐
  • 클래스의 이름이 명시되는 클래스
class Animal{
    void performBehavior(){
        class Brain{}    //지역 내부 클래스 정의, 클래스가 선언된 메서드 블록 내에서만 사용 가능
    }
}

이름이 없는 지역 내부 클래스
  • 이름을 갖지 않는 내부 클래스
  • new 예약어 뒤에 명시된 클래스가 기존의 클래스인 경우에 자동적으로 이 클래스의 자식 클래스가 됨
  • 추상클래스의 객체를 내부 클래스 형태로 생성할 때 자주 사용
abstract class TV{                                    // 추상 클래스 정의
    public abstract void powerOn();
    public abstract void powerOff();
}

class AnonymousTest{

    public static void watchTV(TV tv){                // 임의의 메서드 정의
        tv.powerOn();
        tv.powerOff();
    }

    public static void main(String[] args){
        watchTV(new TV(){                            // 무명 클래스를 이용하여 메서드를 호출하며
            public void powerOn(){                    // 추상 클래스의 기능을 사용할 수 있게 됨
                System.out.println("TV---전원 켬")
            }
            public void powerOn(){
                System.out.println("TV---전원 끔")
            }
        })
    }
}

TV라는 추상 클래스의 객체를 내부 클래스 형태로 생성했기 때문에 실제로는 TV클래스를 상속한 내부 클래스가 만들어지게 됨.



■ 객체의 형변환

객체의 형변환 개요

  • 부모 클래스 유형을 자식 클래스 유형으로 강제 형변환하는 경우 할당되는 인스턴스 유형에 따라 실행오류 발생
  • instanceof 연산자를 사용해 생성된 객체가 class와 관계있는 type으로 만들어졌는지 확인(true/false)
class Employee{}
class MAnager extends Employee{}

public class InstanceOfTest{
    public static void main(String args[]){
        Manager m = new Manager();
        Employee e = new Employee();
        System.out.println(m instanceof Manager);    // true
        System.out.println(m instanceof Employee);    // true
        System.out.println(e instanceof Manager);    // false
    }
}

Manager객체 m은 Manager 타입임과 동시에 Employee 타입

Employee 객체는 Manager클래스가 가진 새로운 특징은 가지고 있지 않으므로 오직 Employee 타입


객체의 형변환

  • 클래스의 형변환은 기본적으로 상속 관계까 아닌 클래스 사이에는 발생X
  • 자식 클래스의 객체는 부모 타입의 참조 변수에 할당될 수 있음(Promotion)
class Employee{
    String name;
    int employeeNo;
    int departmentNo;

    public String getEmployeeInfo(){ ... }
}
class Manager extends Employee{
    Employee[] employeeList;                // 변수 추가

    public String getManagerInfo(){ ... }    // Overriding
}
  • Employee 객체에 접근할 수 있는 경우

Employee e1 = new Employee();

// 변수 타입과 객체가 모두 Employee

// e1 참조변수를 통해 Emplyee 객체가 가지고 있는 변수, 메서드 모두 접근 가능


  • Employee, Manager 객체에 모두 접근할 수 있는 묵시적 형변환인 경우

Manager m1 = new Manager();

// 변수 타입과 생성된 객체가 모드 Manager

// Employee객체를 상속받았으니 객체 생성시 Manager객체와 Employee객체 생성(promotion)

// Manager 객체 생성 시 Employee가 가진 속성이 메모리상에 로딩됨

// m1 참조변수를 통해 Emplyee,Manager 객체의 변수, 메서드 모두 접근 가능


  • 생성된 객체와 참조하는 변수의 타입이 다른 경우

Employee e2 = new Manager();

// Manager는 Employee객체가 가진 변수와 메서드를 모두 가지고 있음

// 참조 변수의 타입이 Employee이므로, 접근 가능한 변수와 메서드는 Employee객체로 제한됨

// 문제가 있는 소스코드.


  • Employee, Manager 객체에 모두 접근할 수 있는 Demotion인 경우

Manager m2 = (Manager)e2;

// Employee형의 참조 변수를 Manager형으로 명시적 형변환

// m2는 Manager타입이므로 e2가 참조하고 있던 Manager 객체의 모든 멤버필드에 접근가능(문제 없음.)



class TVFactory{
    public TV getTV(String tvName){
        if(tvName.equals("S사")){
            return new S_TV();
        } else if(tvName.equals("L사")){
            return new L_TV();
        }
        else return null;
    }
}

class abstractTVUser{
    public static void main(String[] args){
        TVFactory factory = new TVFactory();
        TV tv1 = factory.getTV("S사");
        TV tv2 = factory.getTV("L사");
    }
}

모든 TV클래스들의 최상위 부모인 TV타입의 변수로 받을 수 있음

명령형 매개변수가 무엇인지에 따라 다른 TV객체가 실행되고, 소스코드의 수정은 발생되지 않음.

  • 형변환에 참여한 서로 상속 관계에 있는 두 클래스 간에 동일한 이름의 변수가 존재하거나 메서드가 Overriding되어 있을 수 있으니 생성된 객체 변수를 통해 멤버에 접근할 때 주의해야 함
class Parent{
    int num = 10;
    void printNum(){
        System.out.println(num);
    }
}

class Child extends Parent{
    int num = 20;                    // 동일한 이름의 변수가 부모 클래스에 존재하므로 상속X
    void printNum(){                // 메소드 Overriding
        System.out.println(num);
    }
}

public class ObjectCastTest{
    public static void main(String args[]){
        Parent p = new Child();                // Promotion
        p.printNum();                        // 20 출력
        System.out.println(p.num);            // 10 출력
    }
}

p.printNum() -> 메서드 호출은 할당되는 인스턴스에 의해 결정됨

p.num -> 변수에 대한 접근은 객체의 유형에 의해 결정됨


** ∴ 객체 참조 변수가 변수나 메서드를 참조하는 경우, 참조 관계를 결정하는 시간이 다르기 떄문에 나타나는 차이임.**







[참고] : SWExpertAcademy