본문 바로가기

학원/복기

0411 - instanceof, 오버라이드에 의한 다향성, Object클래스, 추상 클래스, 추상 메소드, final 키워드, 상수필드

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

상속 이용해 학원인적자원(강사,학생,직원) 관리 프로그램 작성하기

 
프로그램 작성에 필요한 클래스)
AcademyApp 클래스  - 학원인적자원(강사,학생,직원) 관리 프로그램 
AcademyStudent 클래스  - 학생정보(학생번호,학생이름,수강과목)를 저장하기 위한 클래스
AcademyInstructor 클래스  - 강사정보(강사번호,강사이름,강의과목)를 저장하기 위한 클래스 
AcademyStaff 클래스 - 직원정보(직원번호,직원이름,근무부서)를 저장하기 위한 클래스 
 
 
중복된 코드를 최소화 하기 위해, 상속을 이용하자. 공통적인 속성(번호,이름)에 대한 행위를 가진 클래스를 만들고 해당 클래스를 상속받아  AcademyStudent, AcademyInstructor, AcademyStaff 클래스를 생성하는 것이 더 효율적이다.

                                                        
 
사람정보(번호,이름)를 저장하기 위한 부모클래스인 AcademyPerson 클래스 생성

package inheritance;

//사람정보(번호, 이름)를 저장하기 위한 클래스
// => 학생, 강사, 직원에 대한 공통적인 속성과 행위를 표현하기 위한 클래스 
// => 학원인적자원 관련 클래스가 반드시 상속받아야 되는 부모클래스
public class AcademyPerson {
	//필드
	private int num;
	private String name;
	
	//생성자
	public AcademyPerson() {
		// TODO Auto-generated constructor stub
	}

	public AcademyPerson(int num, String name) {
		super();
		this.num = num;
		this.name = name;
	}
	
	//Setter Getter 메소드
	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	//출력 메소드 
	public void display() {
		System.out.println("번호 = " + num);
		System.out.println("이름 = " + name);
	}
}

 
부모인클래스인 AcademyPerson 클래스를 상속받아 학생정보, 강사정보,직원정보가 저장된 각각의 자식클래스 생성
 
AcademyStudent 클래스 생성
 

package inheritance;

//학생정보(학생번호, 학생이름, 수강과목)를 저장하기 위한 클래스
// => 학생번호와 학생이름 관련 속성과 행위는 AcademyPerson 클래스를 상속받아 작성 
public class AcademyStudent extends AcademyPerson {
	//필드
	private String course; //수강과목 
	
	//생성자
	public AcademyStudent() {
		// TODO Auto-generated constructor stub
	}

	//부모클래스의 매개변수가 있는 생성자 
	public AcademyStudent(int num, String name, String course) {
		super(num, name);
		this.course = course;
	}

	//Setter Getter 메소드
	public String getCourse() {
		return course;
	}

	public void setCourse(String course) {
		this.course = course;
	}
	
	//출력 메소드
	//오버라이드 통해 부모클래스 메소드를 다시 선언
	@Override
	public void display() {
		// TODO Auto-generated method stub
		System.out.println("학생번호 = " + getNum()); //private 돼있기 때문에 getter 메소드 이용
		System.out.println("학생이름 = " + getName());
		System.out.println("수강과목 = " + course);
		
	}
}

 
AcademyInstructor 클래스 생성

package inheritance;

//강사정보(강사번호, 강사이름, 강의과목)를 저장하기 위한 클래스 
// => 강사번호와 강사이름 관련 속성과 행위는 AcademyPerson 클래스를 상속받아 작성 
public class AcademyInstructor extends AcademyPerson {
	//필드
	private String subject;
	
	//생성자
	public AcademyInstructor() {
		// TODO Auto-generated constructor stub
	}
	
	//부모클래스의 매개변수가 있는 생성자 
	public AcademyInstructor(int num, String name, String subject) {
		super(num, name);
		this.subject = subject;
	}
	
	//Setter Getter 메소드
	public String getSubject() {
		return subject;
	}

	public void setSubject(String subject) {
		this.subject = subject;
	}
	
	//출력 메소드 
	//오버라이드 통해 부모클래스 메소드를 다시 선언
	@Override
	public void display() {
		System.out.println("강사번호 = " + getNum()); 
		System.out.println("강사이름 = " + getName());
		System.out.println("강의과목 = " + subject);
	}	
}

 
 
AcademyStaff 클래스 생성 

package inheritance;

//직원정보(직원번호, 직원이름, 근무부서)를 저장하기 위한 클래스 
//=> 직원번호와 직원이름 관련 속성과 행위는 AcademyPerson 클래스를 상속받아 작성 
public class AcademyStaff extends AcademyPerson {
	//필드
	private String depart;
	
	//생성자
	public AcademyStaff() {
		// TODO Auto-generated constructor stub
	}

	//부모클래스의 매개변수가 있는 생성자 
	public AcademyStaff(int num, String name, String depart) {
		super(num, name);
		this.depart = depart;
	}
	
	//Setter Getter 메소드
	public String getDepart() {
		return depart;
	}

	public void setDepart(String depart) {
		this.depart = depart;
	}
	
	//출력 메소드 
	//오버라이드 통해 부모클래스 메소드를 다시 선언
	@Override
	public void display() {
		System.out.println("직원번호 = " + getNum()); 
		System.out.println("직원이름 = " + getName());
		System.out.println("근무부서 = " + depart);
	}
}

 
학원인적자원 관리 프로그램 작성 위한 AcademyApp 클래스 생성 
 
사람정보(AcademyPerson 객체)를 저장하기 위한 요소들이 존재하는 배열을 생성한다.
AcademyPerson은 AcademyStudent, AcademyInstructor, AcademyStaff의 부모이며, 부모는 모든 자식들을 저장할 수 있다. 따라서, 배열요소에 (AcademyStudent 객체), 강사정보(AcademyInstructor 객체), 직원정보(AcademyStaff 객체) 저장이 가능하다. 이는 부모클래스의 참조변수에 자식클래스의 생성자로 (부모클래스의) 객체를 생성하여 저장이 가능하기 때문이다. 
 
 

public class AcademyApp {
	public static void main(String[] args) {
		AcademyPerson[] persons = new AcademyPerson[5];
        //자식클래스의 생성자로 객체를 생성하면 부모클래스의 객체를 먼저 생성한 후 
		//자식 클래스의 객체를 생성
		// => 배열 요소에는 부모클래스의 객체가 저장되어 부모클래스의 메소드를 호출 
		// => 객체 형변환을 이용하면 배열 요소를 자식 클래스의 객체를 일시적으로 저장하여 
		//자식클래스의 메소드 호출 가능 
		persons[0] = new AcademyStudent(1000,"홍길동","웹개발자 과정");
		persons[1] = new AcademyInstructor(2000,"임꺽정","Java 과목");	
		persons[2] = new AcademyStaff(3000,"전우치","운영관리팀");
		persons[3] = new AcademyStudent(4000,"일지매","웹디자인 과정");
		persons[4] = new AcademyStaff(5000,"장길산","경영회계팀");
	}
}

 
 
 
오버라이드 선언되지 않은 자식 클래스의 메소드를 호출하기 위해 명시적 객체 형변환을 이용하여 참조변수에 자식클래스의 객체를 일시적으로 저장하여 자식 클래스의 메소드를 호출할 수 있다.
이 때의 문제점은 상속관계가 아닌 클래스로 명시적 객체 형변환을 할 경우 ClassCastException 발생가 발생한다는 것이다. 이를 해결하기 위해서 참조변수로 객체 형변환 가능한 클래스를 확인한 후 명시적 객체 형변환을 이용해야 한다.
>> instanceof 연산자를 사용하여 참조변수의 객체 형변환 가능 클래스를 검사할 수 있다.
 

 
• intstanceof 연산자 : 참조변수로 참조 가능한 클래스를 확인하여 [false] 또는 [true]를 제공하는 연산자

형식) 참조변수 instanceof 클래스

 

다형성 
• 오버로드에 의한 다형성: 매개변수에 전달되는 값에 따라 메소드를 선택 호출

• 오버라이드에 의한 다향성 : 객체 형변환에 의해 참조변수에 저장된 객체에 따라 다른 자식클래스의 메소드를 선택해 호출

public class AcademyApp {
	public static void main(String[] args) {
    
    //배열 요소에 저장된 객체를 하나씩 제공받아 참조변수에 저장하여 일괄처리 
    for(AcademyPerson person : persons) {
    	if(person instanceof AcademyStudent) {
				System.out.println(((AcademyStudent)person).getCourse() + "의 학생정보 >> ");
			} else if(person instanceof AcademyInstructor) {
				System.out.println(((AcademyInstructor)person).getSubject() + "의 강사정보 >> ");
			} else if(person instanceof AcademyStaff) {
				System.out.println(((AcademyStaff)person).getDepart() + "의 직원정보 >> ");			
			}
            
            //오버라이드 선언된 메소드는 묵시적 객체 형변환에 의해 부모클래스의 메소드를 호출하지 않고 자식 클래스의 메소드를 호출
			//참조변수에 저장된 자식클래스의 객체에 의해 자식클래스의 메소드가 선택 호출된다
			// => 오버라이드에 의한 다형성 : 객체 형변환에 의해 참조변수에 저장된 객체에 
			//따라 다른 자식클래스의 메소드를 선택 호출 
			person.display();
	}
}

 


 
클래스 선언시 상속받은 부모클래스가 없는 경우 기본적으로 Object 클래스를 자동으로 상속받는다. 
모든 Java 클래스는 무조건 Object 클래스를 상속받아 사용 가능하다. 

 
• Object 클래스 : 모든 Java 클래스의 최선조 클래스 

=> Object 클래스로 생성된 참조변수에는 모든 클래스로 생성된 객체를 저장 or 반환이 가능하다 (중요) 

 

 Object.toString() 

: 참조변수에 저장된 객체를 참조하여 "클래스명@메모리주소" 형식의 문자열로 변환하여 반환하는 메소드
 
 

예시) 자동차정보(모델명, 소유자명)를 저장하기 위한 클래스 Car와 실행 프로그램 CarApp 작성

 

public class Car /* extends Object */ { 
	//필드
	private String modelName;
	private String userName;
	
	//생성자
	public Car() {
		// TODO Auto-generated constructor stub
	}

	public Car(String modelName, String userName) {
		super();
		this.modelName = modelName;
		this.userName = userName;
	}
	
	//Setter Getter 메소드
	public String getModelName() {
		return modelName;
	}

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

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}
	
	//Object 클래스의 toString() 메소드를 오브라이드 선언 
	// => Object 클래스의 toString() 메소드는 숨겨지고, Car 클래스의 toString() 메소드가 호출된다
	// => VO 클래스에서는 필드값을 문자열로 변환하여 반환하는 명령 작성 - 필드값 확인 
	@Override
	public String toString() {
		return "모델명 = " + modelName + ", 소유자 = " + userName;
	}
	
}

 

public class CarApp {
	public static void main(String[] args) {
		Car car = new Car("싼타페","홍길동");
		
		System.out.println("모델명 = " + car.getModelName());
		System.out.println("소유자명 = " + car.getUserName());
		System.out.println("===================================================================");
		
		/*
		//Object.toString() : 참조변수에 저장된 객체를 참조하여 "클래스명@메모리주소" 형식의
		//문자열로 변환하여 반환하는 메소드 
		System.out.println("car.toString() = " + car.toString()); //car.toString() = inheritance.Car@3b6eb2ec
		//참조변수를 출력할 경우 자동으로 toString() 메소드 호출 
		System.out.println("car.toString() = " + car); //car.toString() = inheritance.Car@3b6eb2ec
		*/
		
		//Object 클래스의 toString() 메소드가 아닌 Car 클래스의 toString() 메소드 자동 호출 
		System.out.println("car = " + car); //car = 모델명 = 싼타페, 소유자 = 홍길동
		System.out.println("===================================================================");
		//문자열이 저장된 String 객체를 생성하여 객체의 메모리주소를 참조변수에 저장 
		String name = "홍길동"; //'홍길동'이라는 문자열을 저장한 String객체를 만들어 String객체의 
								//메모리 주소를 name 이라는 참조변수에 저장하는 것 
		
		//String 클래스에 오버라이드 선언된 toString() 메소드 호출 
		// => String.toSTring() : String 객체에 저장된 문자열을 반환하는 메소드 
		
		System.out.println("name = " + name.toString()); //name = 홍길동
		
		//String 클래스의 참조변수를 출력할 경우 자동으로 String 클래스의 toString() 메소드 호출 
		System.out.println("name = " + name); //name = 홍길동
		System.out.println("===================================================================");
	}
}

 
이처럼 VO클래스 만들 때 일반적으로
값을 저장하기 위한 필드, 필드 초기화 위한 생성자, Setter Getter메소드 , toString 메소드를 오버라이드해서 필드값들을 문자열로 변환하는 메소드까지 만들어준다

 
 


 

• 추상클래스(Abstract Class) : abstract 제한자를 사용하여 선언된 클래스 

=> 객체 생성이 불가능한 ' 상속 전용 클래스 '
=> abstract 제한자 : 클래스와 메소드에 설정에 사용 가능한 제한자 

형식) public abstract class 클래스명 { }

 

• 추상메소드(Abstract Method) : abstract 제한자를 사용하여 선언된 메소드 

  => 메소드의 머릿부만 작성하고 몸체부는 작성하지 않는 미완성된 메소드 

형식) 접근제한자 abstract 반환형 메소드명(자료형 매개변수명, ...);

 => 추상메소드가 선언된 클래스는 반드시 추상클래스로 선언해줘야 한다. 
 => 추상메소드가 선언된 클래스를 상속받은 자식클래스는 무조건 모든 추상메소드를 오버라이드 선언해줘야 한다 
    - 자식클래스에서 추상메소드를 오버라이드 선언하지 않으면 자식클래스도 추상클래스로 설정되어 객체를 생성할 경우 에러 발생
 
자식클래스에서 부모클래스의 메소드를 무조건 오버라이드 선언하도록 설정하기 위해 abstract 제한자를 사용하여 추상메소드로 선언하는 것이다.
 
 


 

• final 제한자 : 클래스, 메소드, 필드에 사용하는 제한자 

 
1.final 제한자를 필드에 사용하여 선언 - final 필드
=> final 필드값 변경 불가능 - 필드에 저장된 값을 변경할 경우 에러 발생 (초기값을 절대 바꿀 수 없도록 하기 위해 사용)  
=> 필드를 선언할 때 반드시 초기값을 필드에 저장시켜야 한다 
 

형식) 접근제한자 final 자료형 필드명 = 초기값;


2. final 제한자를 메소드에 사용하여 선언 - final 메소드 
=> 자식클래스에서 메소드를 오버라이드 선언하지 못하도록 제한하는 기능 제공  

 

형식) 접근제한자 final 반환형 메소드명(자료형 매개변수명, ...) { }


3. final 제한자를 클래스에 사용하여 선언 - final 클래스

=> final 클래스를 상속 받지 못하도록 제한하는 기능 제공 

형식) 접근제한자 final class 클래스명 {  }

 
 

• 상수필드(Constant Field) 

: 프로그램에서 값(리터럴) 대신 사용하기 위한 의미있는 단어로 제공되는 필드값   
=> 상수명은 대문자로 작성하여 스네이크 표기법을 이용하여 작성하는 것을 권장한다. 

 
장점 : 상수를 보고 의미를 파악할 수 있으므로 프로그램을 유지보수할때 효율적이다  

 
 

형식) public static final 자료형 변수명 = 초기값;

 
ex)

public static final double INCENTIVE_RATE = 1.5;