본문 바로가기

학원/복기

0410 - 클래스 다이어그램, 상속관계, 포함관계, Super 키워드, 메소드 오버라이딩, 객체형변환

클래스 다이어그램


클래스와 클래스의 관계 - 객체 관계
→ UML(Unified Modeling Language)를 사용하여 클래스 다이어그램(Class Diagram)으로 표현할 수 있다.

** UML : 통합 모델링 언어 >> 모델을 만들고 설명하는 표준 언어(약속) 


1. 일반화 관계(Generalization) : 상속 관계 - X is a Y (X는 Y이다)
→ 클래스를 선언할 때 기존 클래스를 상속받아 작성 
→ 사원 클래스와 관리자 클래스의 관계 - 관리자는 사원이다.(0), 사원은 관리자다.(X) 

2. 실체화 관계(Realization) : 상속 관계 
 클래스를 선언할 때 기존 인터페이스를 상속받아 작성 
 인터페이스 : 현실에 존재하는 대상을 클래스 보다 추상적으로 표현하기 위한 자료형 

자바에서는 위의 일반화 관계, 실체화 관계 모두 '상속 관계'로 본다 (UML에서는 따로 구분)

3. 연관 관계(Association) : 포함 관계 - X has a Y  (X는 Y를 가지고 있다)
 직접 연관 관계(Direct Association) : 한 방향으로만 도구로써 기능을 제공하는 관계 
 컴퓨터 << CPU + MainBoard + Memory  (CPU가 바뀌면 컴퓨터에 영향)  

4. 집합 연관 관계(Aggregation) : 포함 관계로 설정된 객체들의 생명주기가 다른 포함 관계 
 컴퓨터 << 프린터  

5. 복합 연관 관계(Composition) : 포함 관계로 설정된 객체들의 생명주기가 같은 포함 관계 
 게임 << 캐릭터 (게임이 종료되면 캐릭터도 같이 소멸 ) 

6. 의존 관계(Dependency) : 포함 관계로 설정된 객체가 변경돼도 다른 객체에 영향을 주지 않는 포함 관계
 TV << 리모콘  (리모콘이 바껴도 TV에 영향을 주지 않음) 
 
 
 
 
 

포함관계 예시


 
Engine Class  - 엔진 정보를 저장하기 위한 클래스 
Car Class  - 자동차 정보(모델명, 생산년도, 엔진정보)를 저장하기 위한 클래스 
 
 
 
 
Engine Class 생성
 
 

public class Engine {
    //필드 생성
    private String fualType; //연료타입
    private int displacement; //배기량


    //생성자 생성
    public Engine() {
        // TODO Auto-generated constructor stub
    }

    public Engine(String fualType, int displacement) {
        super();
        this.fualType = fualType;
        this.displacement = displacement;
    }

    //Getter Setter 메소드
    public String getFualType() {
        return fualType;
    }

    public void setFualType(String fualType) {
        this.fualType = fualType;
    }

    public int getDisplacement() {
        return displacement;
    }

    public void setDisplacement(int displacement) {
        this.displacement = displacement;
    }


    //엔진정보(필드값)를 출력하는 메소드 (원래 생성하지 않아도  편리성 위해 임의로 생성)
    public void displayEngine() {
        System.out.println("연료타입 = " + fualType);
        System.out.println("배기량 = " + displacement);
    }
}

 
 
Car Class 필드 생성
 

public class Car {
    private String modelName;
    private int productionYear;
    //엔진정보를 저장하기 위한 필드 - Engine 클래스를 자료형으로 선언된 필드
    // => 필드에는 Engine 객체를 생성자 또는 Setter 메소드를 사용하여 제공받아 저장 - 포함관계
    private Engine carEngine; // >> Car클래스와 Engine클래스는 포함관계
}

 
 
Car Class 생성자 생성
 

public class Car {
	 public Car() {
        // TODO Auto-generated constructor stub
    }

    public Car(String modelName, int productionYear, Engine carEngien) {
        super();
        this.modelName = modelName;
        this.productionYear = productionYear;
        this.carEngine = carEngien;
    }
}

 
 
Car Class 메소드 생성
 

public class Car {
     //Setter Getter 메소드

        public String getModelName() {
            return modelName;
        }

        public void setModelName(String modelName) {
            this.modelName = modelName;
        }

        public int getProductionYear() {
            return productionYear;
        }

        public void setProductionYear(int productionYear) {
            this.productionYear = productionYear;
        }

        public Engine getCarEngien() {
            return carEngine;
        }

        public void setCarEngien(Engine carEngine) {
            this.carEngine = carEngine;
        }

        //자동차 정보(필드값)를 출력하는 메소드
        public void displayCar() {
            System.out.println("모델명 = " + modelName);
            System.out.println("생산년도 = " + productionYear);
        
        //엔진 정보 출력하는 메소드
        
        //1)
        //System.out.println("엔진정보  = " + carEngine); //객체의 메모리 주소 출력 >> 잘못된 방식

        //2)
        //필드에 저장된 객체를 이용하여 메소드 호출
        //=> 포함관계로 설정된 클래스(객체)의 메소드 호출하여 원하는 기능 구현
        //=> 포함관계가 설정되지 않은 상태에서 메소드가 호출될 경우 NullPointerException 발
        //System.out.println("연료타입 = " + carEngine.getFualType());
        //System.out.println("배기량 = " + carEngine.getDisplacement());

        //3)
        carEngine.displayEngine(); //코드의 중복성 최소화
}

 
 
CarApp Class 생성 - 실행 목적 

 
 
1)Setter 메소드 호출

public class CarApp {
    public static void main(String[] args) {
        //엔진 생성
        Engine engine = new Engine();

        engine.setFualType("경유");
        engine.setDisplacement(2000);

        //engine.displayEngine();

        //자동차 생성
        Car carOne = new Car();

        carOne.setModelName("쏘렌토");
        carOne.setProductionYear(2020);

        //Setter 메소드를 호출하여 매개변수에 엔진정보(Engine 객체)를 전달받아 필드에 저장
        //=> 인위적인 포함관계 성립
        //이 때 포함관계가 완전히 만들어짐
        carOne.setCarEngien(engine);
        carOne.displayCar();

      /*
      모델명 = 쏘렌토
      생산년도 = 2020
      연료타입 = 경유
      배기량 = 2000
      */
	}
}

 
2)생성자 호출
 

public class CarApp {

    public static void main(String[] args) {
    	//자동차 생성 >> 엔진을 생성하여 필드에 저장 - 포함관계 성립
        // => 생성자를 호출하여 매개변수에 엔진정보(Engine 객체)를 전달받아 필드에 저장
        Car carTwo = new Car("싼타페", 2023, new Engine("휘발유",3000));

        carTwo.displayCar();

		/*
		모델명 = 싼타페
		생산년도 = 2023
		연료타입 = 휘발유
		배기량 = 3000
		*/

   }
}

 
엔진정보 출력
 

System.out.println(carOne.getModelName() + "의 엔진정보 >> ");
engine.displayEngine();
/*
쏘렌토의 엔진정보 >>
연료타입 = 경유
배기량 = 2000
*/

//두번째 자동차는 Engine 객체가 변수에 저장되어 있지 않음. 필드에 저장되어 있다.

//자동차 (Car 객체)에 저장된 엔진정보(engine 필드값 - Engine 객체)을 Getter 메소드로
//반환받아 Engine 객체의 메소드 호출
System.out.println(carTwo.getModelName() + "의 엔진정보 >> ");
carTwo.getCarEngien().displayEngine();

 
 
 
 
 
 

상속(Inheritance)


• 상속(Inheritance) : 클래스를 선언할 때 기존 클래스를 물려받아 사용하는 기능
 기존 클래스를 재활용하여 새로운 클래스를 쉽고 빠르게 작성  - 프로그램 생산성 증가
 공통적인 속성과 행위를 포함한 다수의 클래스를 선언할 때 공통적인 속성과 행위의 클래스를 선언하고 작성된 클래스를 상속받아 사용
 코드의 중복성을 최소화 하여 프로그램 생산성 및 유지보수의 효율성 증가

물려주는 클래스 - 부모클래스, 선조클래스, 기본클래스, 슈퍼클래스(SuperClass)
물려받는 클래스 - 자식클래스, 후손클래스, 파생클래스, 서브클래스(SubClass)

형식)

public class 자식클래스 extends 부모클래스 {
       //자식 클래스에서는 부모 클래스의 필드 또는 메소드 사용 가능
   }

 
 부모클래스의 생성자는 자식클래스에게 상속되지 않으며 부모클래스의 은닉화 선언된 필드와 메소드는 자식클래스에서 접근이 불가능하다
 Java에서는 하나의 부모클래스만 상속이 가능하다 - 단일상속
 
 

 

Super 키워드


• super 키워드 자식클래스의 메소드에서 부모클래스 객체의 메모리 주소를 저장하기 위한 키워드
 자식클래스의 메소드에서 부모클래스 객체의 필드 또는 메소드를 참조하기 위해 사용
 자식클래스의 메소드에서 super 키워드를 사용하지 않아도 자식클래스의 메소드에서는 this 키워드로 
    참조되는 필드와 메소드가 없으면 자동으로 부모클래스 객체의 필드 또는 메소드 참조 
 
 
• super 키워드를 사용하는 경우
1. 자식 클래스의 생성자에서 부모클래스의 매개변수가 있는 생성자를 호출하여 초기화 처리하기 위해 super 키워드 사용 
   - 부모클래스 객체가 생성될 때 필드에 원하는 초기값 저장 가능 
 
 
형식)

super(값, 값, ...);

→ 생략된 경우 부모클래스의 매개변수가 없는 기본생성자를 호출하여 객체 생성 


2. 자식클래스의 메소드에서 오버라이드 선언되어 숨겨진 부모클래스의 메소드를 호출할 경우 super 키워드를 사용한다.
 잘 사용하는 방법은 아니다 

	@Override
	public void display() {
		// TODO Auto-generated method stub
		super.display(); //super 키워드로 부모클래스의 숨겨진 메소드를 호출한다 
	}

 
 
 

상속 예시 

부모 클래스인 Member 클래스 생성 - 회원정보(아이디, 이름)를 저장하기 위한 클래스 

package inheritance;

//회원정보(아이디, 이름)를 저장하기 위한 클래스
public class Member {

    //값 저장하는 필드
    private String id;
    private String name;


    //생성자
    public Member() {
        // TODO Auto-generated constructor stub
    }

    public Member(String id, String name) {
        super();
        this.id = id;
        this.name = name;
    }


    //Setter Getter 메소드
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


    //필드값 출력하는 메소드
    public void display() {
        System.out.println("아이디 = " + id);
        System.out.println("이름 = " + name);
    }
}

 
 

자식 클래스인 MemberEvent  클래스 생성 - 이벤트 관련 회원정보(아이디, 이름, 이메일)를 저장하기 위한 클래스

=> 회원정보를 저장하기 위한 Member 클래스를 상속받아 작성하는 것 권장 - 클래스의 재사용성 증가 
 
 
필드 선언 
부모클래스(Member)를 상속받아 사용할 수 있으므로 필드는 선언하지 않아도 된다
부모클래스(Member)에서 상속받은 필드 또는 메소드 미선언

public class MemberEvent extends Member {
    //private String id;
    //private String name;

    private String email;
}

 
생성자 생성-1

public class MemberEvent extends Member {
    public MemberEvent() {
            //super(); //부모클래스의 매개변수가 없는 기본 생성자 호출 - 생략 가능
        }
	
 
	public MemberEvent(String id, String name, String email) {
		super(); //부모클래스의 매개변수가 없는 기본 생성자 호출
				 //이것도 생략 가능

		//자식클래스 메소드에서는 this 키워드로 자식클래스 객체의 필드 또는 메소드를 참조하고
		//자식클래스 객체의 필드 또는 메소드가 없는 경우 super 키워드를 이용하여
		//부모 클래스 객체의 필드 또는 메소드 참조
		// => 부모클래스의 필드 또는 메소드가 은닉화 선언된 경우 자식클래스에서 접근 불가능



		//부모클래스의 필드가 은닉화되어있기 때문에 Setter 메소드 사용해야 한다

		//this.id = id;
		setId(id);
		//this.name = name;
		setName(name);
		this.email = email;
	}
}

 
 
생성자 생성-2 (더 효율적인 방법)
 
[Alt] + [Shift] + [S] >> [O] >> 부모클래스의 생성자 선택 >> 필드 선택 >> Generate
 super 사용해 부모클래스의 매개변수가 있는 생성자 호출 가능
 

public class MemberEvent extends Member {
    public MemberEvent(String id, String name, String email) {
        super(id, name); //부모클래스의 매개변수가 있는 생성자 호출
        this.email = email;
    }
}

 
 
메소드 생성 
부모클래스(Member)에서 상속받은 메소드 생성 X

public class MemberEvent extends Member {
//Setter Getter 메소드 => 생성하지 않아도 된다

    /*
    public String getId() {
       return id;
    }

    public void setId(String id) {
       this.id = id;
    }

    public String getName() {
       return name;
    }

    public void setName(String name) {
       this.name = name;
    }
    */

    public String getEmail() {
        
        return email;
    }

    public void setEmail(String email) {
        
        this.email = email;
    }
}

 
 
상속은 부모클래스의 요소를 그대로 물려받는 것이 장점이자 단점이다. 
상속받은 메소드 중 자식이 쓰기 부적절한 메소드가 존재할 수 있는데, 이는 메소드를 재선언 함으로써 해결할 수 있다
부모클래스의 메소드를 쓰지 않고, 자식클래드가 선언한 메소드를 그대로 사용하는 것 - 메소드 오버라이딩 
 
 

메소드 오버라이딩


• 메소드 오버라이딩(Method Overriding) 상속 관계에서 부모클래스 메소드를 자식클래스에서 재선언하는 기능 
 부모클래스의 메소드를 자식클래스의 객체가 사용하기 부적절한 경우 부모클래스의 메소드를 자식클래스에서 재선언하여 사용하는 방법 
 부모클래스의 메소드(Hide Method)는 숨겨지고 자식클래스의 메소드만 접근 가능
     **숨겨진 부모클래스 : Hide Method 
 
• 메소드 오브라이딩의 작성 규칙  : 부모클래스의 메소드와 같은 접근제한자,반환형,메소드명,매개변수,예외 전달을 사용하여 자식클래스의 메소드를 작성해야한다. 
 
이클립스에서는 부모클래스의 메소드를 자식 클래스에서 오버라이드 선언되도록 자동 완성 하는 기능을 제공한다.
 오버라이드 선언하고 싶은 부모클래스의 메소드명 입력 >> [Ctrl] + [Space] >> Override Method 선택
• @Override : 오버라이드 선언된 메소드를 표현하기 위한 어노테이션
• 어노테이션(Annotation) : 원래는 API 문서에서 특별한 설명을 제공하기 위한 기능의 자료형(인터페이스)을 제공할 수 있도록 만들어진 것이다.  현재는 Java Source 작성에 필요한 특별한 기능을 제공하기 위해 사용되는 자료형으로 쓰인다.
ex) @Override, @Deperecated, @SuppessWarings
 
 
필드값 출력하는 메소드 

public class MemberEvent extends Member {
	@Override
	public void display() {
    	// TODO Auto-generated method stub
    	super.display(); //super 키워드로 부모클래스의 숨겨진 메소드를 호출한다
  	System.out.println("이메일 = " + email);
	}
}

 
 
 

 MemberEvent(자식 클래스) 실행 위한 MemberEventApp 클래스 생성 

 
자식클래스(MemberEvent)의 생성자로 객체를 생성할 경우 부모클래스(Member)의 생성자가 먼저 호출되어 부모클래스(Member)의 객체가 먼저 생성된 후 자식클래스(MemberEvent)의 생성자로 객체를 생성하여 상속관계가 자동으로 성립된다.
 자식클래스의 참조변수에는 자식클래스 객체의 메모리 주소 저장되어 자식클래스 객체의 필드와 메소드를 참조하지만 상속관계에 의해 부모클래스 객체의 필드 또는 메소드 참조 가능

public class MemberEventApp {
    public static void main(String[] args) {
    MemberEvent member1 = new MemberEvent();
    //Setter Getter 이용
    member1.setId("abc123");
    member1.setName("홍길동");
    member1.setEmail("abc@itwill.xyz");

	member1.display();

    /*
    아이디 = abc123
    이름 = 홍길동
    이메일 = abc@itwill.xyz
    */
    
     //생성자 이용
    MemberEvent member2 = new MemberEvent("xyz879","임꺽정","xyz@itwill.xyz");
    member2.display();
    /*
    아이디 = xyz879
    이름 = 임꺽정
    이메일 = xyz@itwill.xyz
    */
    }
}

 

상속관계의 클래스에서 참조변수와 객체와의 관계


 부모클래스의 객체를 생성한다.

부모클래스 참조변수 = new 부모클래스();
// => 부모클래스의 생성자로 객체를 생성하여 부모클래스의 참조변수에 저장 - 가능

 

public class MemberCastApp {
    public static void main(String[] args) {
        Member member1 = new Member();

        //참조변수에 저장된 부모클래스의 객체를 사용하여 부모클래스의 메소드 호출 가능
        member1.setId("abc123");
        member1.setName("홍길동");
        member1.display();

      /*
      아이디 = abc123
      이름 = 홍길동
      */
	}
}

-> 부모만 이용했기 때문에 '상속'은 아니다.
 
 
만들어진 부모클래스의 객체를 자식클래스의 참조변수에 저장한다.
 

자식클래스 참조변수 = new 자식클래스();
// => 부모클래스의 생성자로 부모클래스의 객체를 생성하고 자식클래스의 생성자로 자식 클래스 객체를 생성하여 
//자식클래스의 참조변수에 자식클래스의 객체를 저장
public class MemberCastApp {
    public static void main(String[] args) {
     	MemberEvent member2 = new MemberEvent();

        //참조변수에 저장된 자식클래스의 객체를 사용하여 자식클래스의 메소드를 호출할 수
        //있으며 상속관계에 의해 부모클래스 객체를 참조하여 부모클래스도 호출 가능
        member2.setId("xyz789");
        member2.setName("홍길동");
        member2.setEmail("xyz@itwill.xyz");
        
        member2.display();
        /*
        아이디 = xyz789
	이름 = 홍길동
	이메일 = xyz@itwill.xyz
        */
	}
}

 
부모클래스의 생성자로 부모클래스의 객체를 생성하고 자식클래스의 참조변수에 부모클래스의 객체 저장 - 불가능(에러 발생)
 

자식클래스 참조변수 = new 부모클래스();

//MemberEvent member3 = new Member();

 
부모클래스의 생성자로 부모클래스 객체를 생성하고 자식클래스의 생성자로 자식 클래스 객체를 생성하여 부모클래스의 참조변수에 부모클래스의 객체 저장 - 가능
 

부모클래스 참조변수 = new 자식클래스();

Member member4 = new MemberEvent();

//참조변수에 저장된 부모클래스의 객체를 사용하여 부모클래스의 메소드를 호출 가능
// => 자식클래스의 객체를 참조할 수 없으므로 자식클래스도 호출 불가능

//member4 -> MemberEvent(자식클래스)는 참조할 수 없다
member4.setId("opq456");
member4.setName("전우치");

 

객체형변환


객체 형변환을 이용하면 부모클래스의 참조변수로 자식클래스 메소드 호출이 가능하다.
 명시적 객체 형변환(강제 형변환), 묵시적 객체 형변환(자동 형변환)
 상속관계의 클래스에서만 객체 형변환 사용 가능
 
 
 
• 명시적 객체 형변환 : Cast 연산자를 사용하여 부모클래스의 참조변수 자료형을 자식 클래스로 변경하면 일시적으로 참조변수에 자식클래스의 객체가 자동 저장된다.
 명시적 객체 형변환에 의해 자식클래스로 자료형이 변경된 참조변수는 자식클래스의 객체가 저장되므로 자식클래스의 메소드 호출이 가능하다.
 
 

/*
MemberEvent event = (MemberEvent)member4; //event에는 자식객체의 주소가 저장
event.setEmail("opq@itwill.xyz");
*/

//변수를 만드는것보다 형변환하자마자 메소드를 바로 호출하는 것이 좋다
//객체 연산자(.)보다 Cast 연산자가 먼저 실행될 수 있도록 () 연산자 사용 (형변환을 먼저 해야해서)

((MemberEvent)member4).setEmail("opq@itwill.xyz");

 
 

• 묵시적 객체 형변환 : 부모클래스의 메소드를 자식클래스에서 오버라이드 선언하면 부모클래스의 숨겨진 메소드 대신 자식클래스의 메소드를 호출하기 위해 참조변수의 자료형을 자동으로 자식클래스로 변경하여 일시적으로 자식클래스의 객체가 참조변수에 저장되어 자식클래스의 메소드를 호출한다. 
**오버라이드 되지 않은 것은 형변환 되지 않는다

 

//((MemberEvent)member4).display();
      
member4.display();
/*
아이디 = xyz789
이름 = 홍길동
이메일 = xyz@itwill.xyz
*/

모두 출력되는 이유 => 묵시적 객체 형변환
 
 
 
※ 객체형변환을 해야하는 이유?

부모로 참조변수를 만들고 객체형변환을 이용하면 , 부모 뿐만 아니라 모든 자식들을 다 참조할 수 있기 때문이다. 
단 1대 1인 상속관계인 경우에는, 
자식클래스 참조변수 = new 자식클래스();  >> 의 방법이 더 효율적.