혼자 공부하는 자바 - 7장. 상속

youngtae·2023년 3월 26일
0

자바 이론

목록 보기
6/12
post-thumbnail

상속

class 자식클래스 **extends** 부모클래스 {
	//필드
	//생성자
	//메소드
}
  • 여러개의 부모클래스 상속 불가
  • private 접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외
  • 부모와 자식의 패키지가 다르다면 default도 상속 제외
// 생성자가 명시적으로 선언되지 않았다면 컴파일러는 기본생성자를 생성
public DmbCellPhone() {
	super();
}
public class People {
	public String name;
	public String ssn;

	public People(String name, String ssn) {
		this.name = name;
		this.ssn = ssn;
	}
}
  • 위의 코드에서 People 클래스는 기본 생성자가 없고 name, ssn 생성자만 있음 → People을 상속하는 Student 클래스는 생성자에서 super(name, ssn)으로 생성자 호출 해야함
public class Student extends People {
	public int studentNo;

	public Student(String name, String ssn, int studentNo) {
		super(name, ssn);  // 없으면 에
		this.studentNo = studentNo;
	}
}
  • Student 클래스의 생성자는 name, ssn, studentNo를 매개값으로 받아서 name과 ssn은 다시 부모 생성자를 호출하기 위해 매개값으로 넘겨줌

  • 메소드 재정의 (오버라이딩)

    • 자식클래스에서 부모 클래스의 메소드 다시 정의
      • 부모의 메소드와 동일한 리턴타입, 메소드이름, 매개변수목록 가져야함
      • 접근 제한을 더 강하게 재정의 불가
      • 새로운 예외를 throws 할 수 없음
    • 오버라이딩 메소드 자동생성
      • Source - Override/Implement methods
      • 부모클래스에서 재정의될 메소드 선택
    • 부모 메소드 호출 : super.부모메소드();
  • final

    • 해당 선언이 최종 상태이고 결코 수정될 수 없음
    • 클래스 앞에 final 붙이면 상속 불가
    • 메소드 선언할 때 final 키워드 붙이면 재정의 불가
  • protected 접근제한

    • 다른 패키지일때 접근 불가능하지만 자식클래스인 경우 가능

타입 변환과 다형성

  • 다형성 = 메소드 오버라이딩 + 타입 변환
  • 클래스 자동 타입 변환
    • 상속관계에 있는 클래스 사이에서 발생
    • 자식 → 부모 타입
      Cat cat = new Cat();
      Animal animal = cat;
      // Animal animal = new cat(); 도 가능
      // 두 변수는 동일한 Cat 객체를 참조
    • 부모타입으로 자동 변환된 이후에는 부모클래스에 선언된 필드와 메소드만 접근 가능
    • 변수는 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로만 한정
    • 예외 : 메소드가 자식클래스에서 오버라이딩됐다면 자식 클래스의 메소드가 대신 호출

필드의 다형성

  • 메소드를 수정하지 않아도 다양한 결과 얻을 수 있음
public class Tire {
	//필드
	public int maxRotation;     			//최대 회전수
	public int accumulatedRotation;		//누적 회전수
	public String location;       			//타이어의 위치

	//생성자
	public Tire(String location, int maxRotation) {
		this.location = location;  // 위치 초기화
		this.maxRotation = maxRotation;  // 누적 회전수 초기화
	}

	//메소드
	public boolean roll() {
		++accumulatedRotation;		  //누적 회전수 1 증가
		if(accumulatedRotation<maxRotation) {  // 정상 회전일 경우 실행
			System.out.println(location + " Tire 수면: " + (maxRotation-accumulatedRotation) + "회");
			return true;
		} else {  // 누적 = 최대 회전수 일 경우 실행
			System.out.println("*** " + location + " Tire 펑크 ***");
			return false;
		}
	}
}
public class Car {
	//필드
	Tire frontLeftTire = new Tire("앞/왼", 6);
	Tire frontRightTire = new Tire("앞/오", 2);
	Tire backLeftTire = new Tire("뒤/왼", 3);
	Tire backRightTire = new Tire("뒤/오", 4);
	
	//생성자
	
	//메소드
	int run() {
		//모든 타이어 회전
		//false리턴하는 메소드 있으면 stop 호출하고 해당 번호 리턴
		System.out.println("[자동차가 달립니다.]");
		if(frontLeftTire.roll()==false) { stop(); return 1; };
		if(frontRightTire.roll()==false) { stop(); return 2; };
		if(backLeftTire.roll()==false) { stop(); return 3; };
		if(backRightTire.roll()==false) { stop(); return 4; };
		return 0;
	}
	
	void stop() {  // 펑크 났을때 실행
		System.out.println("[자동차가 멈춥니다.]");
	}
}
public class HankookTire extends Tire {

	//생성자
	public HankookTire(String location, int maxRotation) {
		super(location, maxRotation);
	}	
	//메소드
	@Override //다른내용 출력하기 위해 roll() 오버라이딩
	public boolean roll() {
		++accumulatedRotation;		
		if(accumulatedRotation<maxRotation) {
			System.out.println(location + " HankookTire 수명: " + (maxRotation-accumulatedRotation) + "ȸ");
			return true;
		} else {
			System.out.println("*** " + location + " HankookTire 펑크 ***");
			return false;
		}
	}
}
public class CarExample {
	public static void main(String[] args) {
		Car car = new Car();
		
		for(int i=1; i<=5; i++) {
			int problemLocation = car.run();
			switch(problemLocation) {
				case 1:
					System.out.println("앞/왼 HankookTire로 교체");
					car.frontLeftTire = new HankookTire("앞/왼", 15);
					break;
				case 2:
					System.out.println("앞/오 KumhoTire로 교체");
					car.frontRightTire = new KumhoTire("앞/오", 13);	
					break;
				case 3:
					System.out.println("뒤/왼 HankookTire로 교체");
					car.backLeftTire = new HankookTire("뒤/왼", 14);	
					break;
				case 4:
					System.out.println("뒤/오 KumhoTire로 교체");
					car.backRightTire = new KumhoTire("뒤/오", 17);		
					break;
			}
			System.out.println("----------------------------------------");
		}
	}
}

매개 변수의 다형성

  • 메소드 호출에서도 자동 타입 변환 많이 발생
  • 매개 변수에 자식 객체 지정해서 메소드 호출 가능
public class Vehicle {
    public void run() {
        System.out.println("차량이 달립니다");
    }
}

public class Driver {
    public void drive(Vehicle vehicle) {
        vehicle.run();
    } // drive 메소드에서 Vehicle 타입의 매개값 받아서 run() 메소드 호출
}

public class Bus extends Vehicle {
    @Override
    public void run() {
        System.out.println("버스가 달립니다.");
    }
}

public class Taxi extends Vehicle {
    @Override
    public void run() {
        System.out.println("택시가 달립니다.");
    }
}

public class DriverExample {
    public static void main(String[] args) {
        Driver driver = new Driver();
        Bus bus = new Bus();
        Taxi taxi = new Taxi();

				// 자식 객체가 재정의한 run() 메소드 실행
        driver.drive(bus);  // 자동 타입 변환 Vehicle vehicle = bus; 
        driver.drive(taxi); // 자동 타입 변환 Vehicle vehicle = taxi;
        // 버스가 달립니다.
        // 택시가 달립니다.
    }
}
  • 위의 코드를 통해 매개변수의 타입이 클래스일 경우, 해당 클래스의 객체뿐만 아니라 자식 객체까지도 매개값으로 사용할 수 있다는 것을 알 수 있다.
  • 즉, 매개값으로 어떤 자식 객체가 제공되느냐에 따라 메소드의 실행결과는 다양해질 수 있다.
  • 자식 객체가 부모의 메소드를 재정의했다면 메소드 내부에서 재정의된 메소드를 호출함으로써 메소드 실행결과는 다양해진다.

강제 타입 변환

  • 부모 타입을 자식 타입으로 변환
  • 자식타입이 부모타입으로 자동 변환된 후에만 강제 타입 변환 가능
public class Parent {
    public String field1;
    
    public void method1() {
        System.out.println("Parent-method1");
    }
    public void method2() {
        System.out.println("Parent-method2");
    }
}

public class child extends Parent {
    public String field2;
    
    public void method3() {
        System.out.println("Child-method3");
    }
}

public class ChildExample {
    private static Child ;

    public static void main(String[] args) {
        Parent parent = new Child();  // 자동 타입 변환
        parent.field1 = "data1";
        parent.method1();
        parent.method2();
        
        /* 불가능
        parent.field2 = "data2";
        parent.method3();
         */
        
        Child  = (Child) parent; // 강제 타입 변환
        child.field2 = "yyy";
        child.method3();
    }
}
// 출력결과
Parent-method1
Parent-method2
Child-method3

객체 타입 확인

  • 처음부터 부모 타입으로 생성된 객체는 자식 타입으로 변환 x
  • 그럼 부모 변수가 참조하는 객체가 부모 객체인지 자식 객체인지 확인 하는 방법은? → instanceof 연산자 사용
    public void method(Parent parent) {
    	if(parent instanceof Child) {
    	// parent가 Child 타입으로 생성되었다면 true, 아니면 false

추상 클래스

  • 클래스들의 공통적인 특성을 추출해서 선언한 클래스
  • 추상 클래스가 부모, 실체 클래스가 자식으로 실체 클래스는 추상 클래스의 모든 특성을 물려받고, 추가적인 특성 가질 수 있음

추상 클래스의 용도

  • 공통된 필드와 메소드의 이름을 통일할 목적
    • 작업자가 여러 명일 경우, 실체 클래스마다 필드와 메소드가 다른 이름을 가질 수 있다.
    • 이런 경우 추상 클래스에 필드와 메소드를 선언하고, 상속해서 사용함으로써 이름을 통일 할 수 있다.
  • 실체 클래스를 작성할 때 시간 절약
    • 공통적인 필드와 메소드는 추상 클래스에 선언하고, 다른 점만 실체 클래스에 언언하여 시간 절약 가능하다.

추상 클래스 선언

  • 추상 클래스 선언할 때는 abstract 사용
    • abstract 붙이면 new연산자로 객체 만들지 못하고, 상속을 통해 자식 클래스만 생성 가능
    • public abstract class 클래스 {…}
    • new연산자로 직접 생성자 호출은 안되지만 필드, 생성자, 메소드 선언은 가능 → 자식 클래스 생성해서 상속 받고 해당 메소드 사용 가능
      public abstract class Phone {
          public String owner;
          
          public Phone(String owner) {
              this.owner = owner;
          }
          
          public void turnOn() {
              System.out.println("폰 전원을 켭니다.");
          }
          public void turnOff() {
              System.out.println("폰 전원을 끕니다.");
          }
      }
      
      public class SmartPhone extends Phone {
          public SmartPhone(String owner) {
              super(owner);
          }
          public void internetSearch() {
              System.out.println("인터넷 검색을 합니다.");
          }
      }
      
      public class PhoneExample {
          public static void main(String[] args) {
              //Phone phone = new Phone(); 불가능
              
              SmartPhone smartPhone = new SmartPhone("홍길동");
              
              smartPhone.turnOn();  //Phone 메소드
              smartPhone.internetSearch();
              smartPhone.turnOff();
          }
      }
      // 출력결과
      폰 전원을 켭니다.
      인터넷 검색을 합니다.
      폰 전원을 끕니다.

추상 메소드와 재정의

  • 추상 클래스는 추상 메소드 선언 가능
  • abstract 키워드와 함께 선언부만 있고 실행 내용인 {}가 없는 메소드
  • public abstract void 메소드이름();
  • 하위 클래스가 반드시 실행 내용을 채우도록 강제하고 싶은 메소드가 있는 경우 해당 메소드를 추상 메소드로 선언, 하지 않으면 에러 발생
public abstract class Animal {
    public String kind;
    
    public void breathe() {
        System.out.println("숨을 쉽니다.");
    }
    public abstract void sound(); //추상 메소드
}

public class Dog extends Animal {
    public Dog() {
        this.kind = "포유류";
    }
    
    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}

public class Cat extends Animal {
    public Cat() {
        this.kind = "포유류";
    }
    
    @Override
    public void sound() {
        System.out.println("야옹");
    }
}

public class AnimalExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();
        dog.sound();
        cat.sound();
        // 멍멍
        // 야옹
        
        Animal animal = null;
        // 자동 타입 변환 및 재정의된 메소드 호출
        animal = new Dog();
        animal.sound();
        animal = new Cat();
        animal.sound();
        // 멍멍
        // 야옹
        
        // 메소드 다형성
        animalSound(new Dog());
        animalSound(new Cat());
    }
    
    public static void animalSound(Animal animal) { // 자동 타입 변환
        animal.sound();  // 재정의된 메소드 호출
    }
    // 멍멍
    // 야옹
}
profile
나의 개발기록

0개의 댓글