기초단계/JAVA

2022.12.15 JAVA 복습 상속

춘핑이 2022. 12. 15. 22:05

7. 상속

7.1 상속개녕

상속은 부모가 자식에게 물려주는 행위 객체 지행 프로그램에서도 부모클래스의 필드와 메소드를 자식클래스에 물려줄수 있다.
상속은 이미 잘 개발된 클래스를 재사용해서 새로운 클래스를 만들기 때문에 중복되는 코드를 줄여 개발 시간을 단축시킨다.
자식클래스 b에서 처음부터 필드와 메소드 4개를 작성하는 것보다는 필드1 메소드1 을 상속받고 추가 작성하는 것이 보다 효율적이다.

public class A
{
inf field1;
void method1(){...}

public class B extends A //A를 상속
{
    String filed2;
    void menthod2(){....}
}

마치 B가 필드1과 메소드1을 가지고 있는 것 처럼 보인다.
상속의 또다른 이점은 클래스의 수정을 최소화 할 수 있다. 부모 클래스를 수정하면 모든 자식 클래스에 수정효과를 가져옴
예를들어 B,C가 A를 상속할 경우 A의 필드와 메소드를 수정하면 BC를 수정하지 않아도 수정된 A필드와 메소드를 이용가능

7.2클래스상속

현실에서 상속은 부모가 자식을 선택해서 물려주지만 프로그램에서는 자식이 부모를 선택한다.
자식 클래스를 선언할때 어떤 부모로부터 상속받을 것인지를 결정하고 부모 클래스를 다음과 같이 extends뒤에 기술한다.
public class 자식클래스 extends 부모클래스{}
다른언어와 달리 자바는 다중상속을 허용하지 않는다. 즉 여러개의 부모클래스를 상속할 수 없다. extends뒤에는 오직하나의 부모만 올수 있다.

7.3 부모 생성자 호출

현실에서 부모 없는 자식이 있을 수 없듯이 자바에서도 자식 객체를 생성 하면 부모 객체가 먼저 생성된다음 자식객체가 생성된다.
자식클래스 변수 = new 자식클래스(); 은 자식클래스만 생성되는 것 처럼 보이지만 부모 클래스 객체가 먼저 생성되고 그 다음 자식 객체가 생성된 것이다.
코드자체만보면 자식만 만드는 것처럼 보이는데 SmartPhone을만들면 Phone객체도 만들어진다.
그러면 부모객체를 만들때 생성자가 있어야 만들어지는데 어디서 호출이되나??
모든 객체는 생성자를 호출해야만 생성된다. 부모 객체도 예외는 아니다. 그렇다면 부모 객체의 생성자는 어디서 호출 된것일가 이것에 대한 비밀은 자식생성자에 숨어잇다.
부모 생성자는 자식 생성자의 맨첫줄에 숨겨져잇는 super()에 의해 호출된다.
자식 생성자 선언

public 자식클래스(...) {
super();
...}

super()는 컴파일 과정에서 자동 추가되는데 이것은 부모의 기본생성자를 호출한다. 만약 부모 클래스에 기본생성자가 없다면 자식 생성자 선언에서 컴파일에러가 발생한다.
부모 클래스에 기본 생성자가 없고 매개변수를 갖는 생성자만 있다면 개발자는 다음과 가이 super(매개값, ...)코드를 직접 넣어야한다.
이코드는 매개값의 타입과 개수가 일치하는 부모 생성자를 호출한다.
부모 생성자에 매개값이 없다면 그냥 소환할수있는데 매개값이 있다면 매개변수를 줘야할수도잇다.
super(model, color); 만약 부모생성자에 매개변수가 있다면 무조건 작성해줘야함.

package ch07ver2.sec01;
public class Phone {
//필드 선언
public String model;
public String color;

//기본 생성자 선언
public Phone()
{
    System.out.println("Phone() 생성자 실행");
}}

package ch07ver2.sec01;
public class SmartPhone extends Phone{
//자식생성자 선언
public SmartPhone (String model, String color)
{
    super(); //생략가능 컴파일시 자동추가됨
    //super(model, color); //만약 부모생성자에 매개변수가 있다면 무조건 작성해줘야함.
    this.model = model;
    this.color = color;
    System.out.println("SmartPhone(String model, String color) 생성자 실행됨");
}}

package ch07ver2.sec01;
public class SmartPhoneExample {
public static void main(String[] args) {
    //스마트폰 객체 생성
    SmartPhone myPhone = new SmartPhone("갤럭시", "은색");

    //Phone로부터 상속받은 필드 읽기
    System.out.println("모델: " + myPhone.model);
    System.out.println("색상: " + myPhone.color);

7.4 메소드 재정의

메소드를 다시 정의한다.
부모클래스의 모든 메소드가 자식클래스에 맞게 설계되엇으면 가장 이상적인 상속이지만
어떤 메소드는 자식클래스가 사용하기에 적합하지 않을 수 잇다.
자식이 맞게끔 수정한다 이를 메소드 재정의, 오버라이딩(overriding)이라고 한다.

7.4.1 메소드 오버라이딩

상속된 메소드를 자식 클래스에서 재정의 하는것 메소드 오버라이딩 되엇다면 부모 메소드는 숨겨지고 자식 메소드가 우선적으로 사용된다.
메소드 오버라이딩을 할때는 다음과 같은 규칙에 주의해서 작성해야한다.
1.부모 메소드의 선언부(리턴타입 메소드 이름 매개변수)와 동일해야한다.
2.접근제한을 더 강하게 오버라이딩할수 없다. (public -> private으로 변경불가)
3.새로운 예외를 throws할 수 없다.(예외는 10장에서 학습)

다음예제 Calculator는 원의 넓이를 구하는 areaCircle() 메소드를 가지고 잇다. 하지만 원주율 파이가 정확하지 않기때문에
Computer 오버라이딩해서 좀더 정확한 원주율파이 상수(Math.PI)를 사용해 원의 넓이를 구하도록한다

자바는 개발자의 실수를 줄여주기 위해 정확히 오버라이딩이 되엇는지를 체크해주는 @Override 이노테이션을 제공한다.
@Override를 붙이면 컴파일 단계에서 정확히 오버라이딩이 되엇는지를 체크하고 문제가잇다면 컴파일에러를 출력한다.

public class Calculator {
//메소드 선언
public double areaCircle(double r)
{
    System.out.println("Calculator 객체의 areaCircle() 실행");
    return 3.14159 * r * r;
}}

public class Computer extends Calculator {
//메소드 오버라이딩 
@Override //컴파일시 정확히 오버라이딩이 되엇는지를 체크해줌(생략 가능)
public double areaCircle(double r)
{
    System.out.println("Computer 객체의 areaCircle() 실행");
    return Math.PI * r * r;}}

public class ComputerExample {
public static void main(String[] args) {
    int r = 10;

    Calculator calculator = new Calculator();
    System.out.println("원 면적: " + calculator.areaCircle(r));
    System.out.println();

    Computer computer = new Computer();
    System.out.println("원 면적2: " + computer.areaCircle(r));
    System.out.println();

7.4.2 부모 메소드 호출

메소드를 재정의하면 부모 메소드는 숨겨지고 자식 메소드만 사용되기 때문에 비록 부모 메소드의 일부만 변경된다하더라도 중복된 내용을 자식도 가지고 잇어야한다.
예를들어 부모 메소드가 100줄의 코들르 가지고 잇을경우 자식메소드에서 1 줄만 추가하고 싶더라도 100줄의 코드를 자식 메소드에서 다시 작성해야한다.
이문제는 자식 메소드와 부모 메소드의 공동 작업 처리기법을 이용하면 매우 쉽게 해결된다. 자식 메소드 내부에서 부모 메소드를 호출하는 것인데다음과 같이 super키워드와 도트(.) 연산자를 사용하면 숨겨진 부모 메소드를 호출 할 수 있다.


super.method()의 위치는 작업 처리2 전후에 어디든지 올 수잇다. 우선 처리가 되어야할 내용을 먼저 작성하면 된ㄴ다.
이 방법은 부모 메소드를 재사용함으로써 자식 메소드의 중복 작업내용을 없애는 효과를 가져온다.
super.method()는 부모의 메소드도 실행하고 추가적으로 실행할 내용이 있을때 사용한다.
Airplane fly() 메소드를 SupersonicAirplane에서 오버라이딩해서 일반 비행모드일땐 Airplane fly()를 사용 초음속 비행모드일때는 오버라이딩 된Super의 fly()

public class Airplane {
//메소드 선언
public void land()
{
    System.out.println("착륙합니다.");
}

public void fly()
{
    System.out.println("일반 비행합니다.");
}

public void takeOff()
{
    System.out.println("이륙합니다.");
}}

public class SupersonicAirplane extends Airplane{
//상수 선언
public static final int NORMAL = 1;
public static final int SUPERSONIC = 2;    

//상태필드선언
public int flyMode = NORMAL; //flyMode = 1;

//메소드 재정의
@Override
public void fly() {
    if (flyMode == SUPERSONIC) //flyMode = 2;
    {
        System.out.println("초음속 비행합니다.");
    }
    else
    {
        super.fly(); //아니라면 부모의 fly();
    }}}

7.5 final클래스와 final메소드

6장 11절에서 살펴보았듯이 필드 선언시에 final을 붙이면 초기값 설정후 값을 변경할 수 없다.
그렇다면 클래스와 메소드에 final을 붙이면? final클래스와 final메소드는 상속과 관련이 있다.

7.5.1 final클래스

클래스를 선언할때 final키워드를 class앞에 붙이면 최종적인 클래스이므로 더이상 상속할 수 없는 클래스가 된다.
즉 final클래스는 부모 클래스가 될수없어 자식클래스를 못만든다.
public final class 클래스()
public class New클래스 extends 클래스{} -------> 불가능
대표적인 예가 String클래스이다. 자식클래스 못만듬.

7.5.2 final 메소드

메소드를 선언할때 final키워드를 붙이면 이 메소드는 최종적인 메소드 이므로 오버라이딩 할수 없는 메소드가 된다.
부모 클래스를 상속해서 자식클래스를 선언할때 재정의가 불가능하다.
public final 리턴타입 메소드(매개변수,...) {...}

7.6 protected 접근제한자

클래스 때 public private 접근 제한자를 사용해 객체 외부에서 필드 생성자 메소드의 접근 여부를 결정햇다.
protected는 상속과 관련이있고 public과 default의 중간쯤에 해당하는 접근제한을 한다.
protected는 같은 패키지에서는 default처럼 접근이 가능하나 다른 패키지에서는 자식클래스만 접근을 허용한다.
protected는 필드 생성자 메소드선언에서 사용가능하다.

7.7 자동타입변환

매우중요!!!!
타입변환이란 타입을 다른 타입으로 변환하는 것을 말한다. 기본타입 변환은 2장에서 학습한 바잇다
클래스도 마찬가지로 타입변환이 잇는데 클래스의 타입변환은 상속관계에 있는 클래스 사이에서 발생한다.

7.7.1 자동타입변환

자동타입은 의미 그대로 자동적으로 타입변환이 일어나는 것을 말한다.
부모 타입변수 = 자식타입객체; 자동타입변환이 이루어짐 / 기본타입 변환에서 넓은 범위 = 좁은 범위 적용하듯이 이루어짐
자식객체을 부모타입 변수에 넣을수있다.


Animal var = new Cat();
Cat cat = new Cat(); //자식객체만들면 부모객체도 생성됨 이용 10번지
Animal animal = cat; //둘다 cat을 참조하게 되는거임. 10번지
Animal animal = new Cat();도 가능
cat == animal true
1.자식객체만들면서 부모 만들어짐 -> 부모를객체를 만드는데 자식을 참조함 ->번지가 같음. ->비교하면 같음

부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근 가능하다.
비록 변수는 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모클래스 멤버로 한정된다.
그러나 자식클래스에서 오버라이딩된 메소드가 잇다면 부모 메소드 대신 오버라이딩된 메소드가 호출된다. 이것은 '다형성' 과 관련되어 있기 때무에 잘알아야한다.
타입변환을 했음에도 불구하고 오버라이딩된게 실행된다.
왜 그렇게되는가? 자동타입변환되면 부모객체가 결국 참조하고 있는 객체가 자식객체이다. 그래서 자식객체의 오버라이딩된 메소드가 실행되는 것이다.
결국 부모가 참조하는 번지가 자식객체의 번지임. 그래서 타입변환이 되었어도 실행되는 것은 자식의 객체
이 특성때문에 다형성이 된다.

public class Parent {
//메소드 선언
public void method1() {
    System.out.println("Parent-method1()");
}

public void method2() {
    System.out.println("Parent-method2()");
}}

public class Child extends Parent{
//메소드 오버라이딩
@Override
public void method2() {
    System.out.println("Child-method2()");
}

//메소드 선언
public void method3() {
    System.out.println("Child-method3()");
}}

public class ChildExample {
public static void main(String[] args) {
    // 자식객체 생성
    Child child = new Child();

    //자동타입변환
    Parent parent = child;

    //메소드 호출
    parent.method1();
    parent.method2();
    //parent.method3(); 호출불가능
}}

7.7.2 강제타입변환

자식타입은 부모 타입으로 자동변환되지만 반대로 부모 타입은 자식 타입으로 자동벼환안되지만 캐스팅 연산자로 강제 타입 변환이 가능하다.
자식타입 변수 = (자식타입) 부모타입객체;
무조건 가능한 것은 아니고 자식 객체가 부모 타입으로 자동변환된후 다시 자식 타입으로 변환할때 강제 타입 변환을 사용할 수 잇다.
Parent parent = new Child(); // 자동타입변환
Child child = (Child) parent(); //강제 타입 변환
자식객체가 부모타입으로 자동변환되면 부모 타입에 선언된 필드와 메소드만 사용가능하다는 제약사항이 생김.
만약 자식타입에 선언된 필드와 메소드를 꼭 사용해야한다면 강제타입 변환을 행한다.

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 {
public static void main(String[] args) {
    // 객체 생성 및 자동타입변환
    Parent parent = new Child();

    //Parent 타입으로 필드와 메소드 사용
    parent.field1 = "data1";
    parent.method1();
    parent.method2();

    //parent.field2 = "data2"; // 자식꺼라 불가능
    //parent.method3() // 자식꺼라 불가능

    //강제타입변환
    Child child = (Child) parent;

    //Child 타입으로 필드와 메서드 사용
    child.field2 = "data2";
    child.method3();
}}

7.8 다형성

매우중요! 자바프로젝트에서 매우 많이나옴. 완전히 이해필요
메소드 재정의, 타입변환을 잘 이해해야함.
다형성이란 사용방법은 동일하지만 실행 결과가 다양하게 나오는 성질을 말한다.
자동차 의 부품을 교환하면 성능이 다르게 나오듯이 객체는 부품과 같아서 프로그램을 구성하는 객체를 바꾸면 프로그램이 실행성능이 다르게 나올수잇다.

다형성을 위해서는 상속이 필요하다. 부수적으로 메소드 재정의와 타입변환
자동차 - 타이어 대입 가능 자식객체 만들어서 대입해도 됨.
자동차 -자식타이어 자동차 -자식타이어 가능
다형성을 위해서는 자동타입변환과 메소드 재정의가 필요하다.

7.8.1 필드다형성

필드 다형성은 필드타입은 동일하지만(사용방법은 동일하지만) 대입되는 객체가 달라져서 실행결고가 다양하게 나올수있는것을 말한다.
Car 클래스에 Tire필드가 선언되어있다.
먼저 Car 객체를 생성한후 타이어를 장착하기 위해 다음과 같이 한국과 금호를 tire필드에 대입할수잇다.

public class Tire {
//메소드선언
public void roll()
{
    System.out.println("회전합니다.");
}}

public class KumhoTire extends Tire {
//메소드 정의
@Override
public void roll() {
    System.out.println("금호타이어가 회전합니다.");
}}

public class HankookTire extends Tire{
//메소드 오버라이딩
@Override
public void roll() {
    System.out.println("한국 타이어가 회전합니다.");
}}

public class Car {
//필드선언
public Tire tire;

//메소드 선언
public void run()
{
    //타이어 객체의 roll()메소드 호출
    tire.roll();
}}

public class CarExample {
public static void main(String[] args) {
    // car객체생성
    Car myCar = new Car();

    //Tire 객체생성
    /*
    Tire tire = new Tire();
    tire.roll();
    */

    myCar.tire = new Tire();
    myCar.run();

    //HankookTire 객체 장착
    /*
    Tire tire2 = new HankookTire();
    tire2.roll();
    */    
    myCar.tire = new HankookTire();
    myCar.run();

    //KumhoTire 객체 장착
    myCar.tire = new KumhoTire();
    myCar.run();
}}

2회독에서 이해되는 부분!
Car클래스에 Tire tire; 해놔서 CarExample에서 사용하면
Car myCar = new Car();
myCar.tire = new Tire();
myCar.run();
이부분이
Tire tire = new Tire();
tire.roll();
이렇게 사용되는거 임.
그런데 자동타입변환이 되니까
Tire tire = new HankookTire();
myCar.tire = new HankookTire(); 이런식이 되는걸 이렇게쓴거임!

7.8.2 매개변수 다형성

다형성은 필드보다는 메소드를 호출할때 많이 발생한다. 메소드가 클래스타입의 매개변수를 가지고잇을경우
호출할때 도일한 타이어 객체를 제공하는 것이 정석이지만 자식 객체를 제공할수도잇다. 여기서 다형성이 필요하다.

public class Vehicle {
//메소드 선언
public void run()
{
    System.out.println("차량이 달립니다.");
}}

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 Driver {
//메소드 선언 (클래스 타입의 매개변수를 가지고있음.)
public void drive(Vehicle vehicle)
//Vehicle vehicle = new Vehicle 
//Vehicle vehicle = new Bus
//Vehicle vehicle = new Taxi
{
    vehicle.run();
}}

public class DriverExample {
public static void main(String[] args) {
    //드라이버 객체생성
    Driver driver = new Driver();

    //매개값으로 Bus객체를 제공하고 driver()메소드 호출

    Bus bus = new Bus();
    //driver.drive(new Bus());
    driver.drive(bus);


    //매개값으로 Taxi객체를 제공하고 driver()메소드 호출
    Taxi taxi = new Taxi();
    //driver.drive(new Taxi());
    driver.drive(taxi);
    }}

2회독에서 이해되는 부분!
public void drive(Vehicle vehicle)매개변수일때
Vehicle vehicle = new Vehicle
Vehicle vehicle = new Bus
Vehicle vehicle = new Taxi
이런식이 됨.
driver.drive(new Bus());
Bus bus = new Bus();
driver.drive(bus);
이렇게 넣을 수 있으니 매개변수에 참조변수를 넣은거임

7.9 객체 타입 확인

매개변수의 다형성에서 실제로 어떤 객체가 매개값으로 제공되었는지 확인하는 방법이있다.
꼭 매게변수가 아니더라도 변수가 참조하는 객체의 타입을 확인하고자 할때 instanceof연산자를 사용할수있다.
boolean result = 객체 instanceof 타입;
Bus bus = new Bus();
Vehilcle v = bus; -> 2회독 Vehilcle v = new Bus(); 이느낌
v instanceof Bus / v라는 변수가 참조하는 객체가 Bus타입으로 만든 객체냐? -> true임.

if (person instanceof Student)
{
    //Student 객체일경우 강제 타입 변환
    Student student = (Student) person;
    //Student 객체만 가지고있는 필드 및 메소드 사용
    System.out.println("studentNo: " + student.studentNo);
    student.study();
    }

강제타입변환없애버리기 java12부터 가능
 if (person instanceof Student student)
 {
     System.out.println("studentNo: " + student.studentNo);
    student.study();
 }

7.10 추상클래스

추상이란 실체가 아닌것? 실체 간에 공통되는 특성을 추출한 것을 의미한다.
추상클래스란? 객체를 생성할 수 있는 클래스를 실체 클래스라고 한다면 이 클래스들의 공통적인 필드나 메소드를 추출해서 선언한 추상클래스라고 한다.
추상클래스는 실체클래스의 부모 역할을 한다. 따라서 실체클래스는 추상클래스를 상속해서 공통적인 필드나 메소드를 물려받을수있다.
A B C를 먼저 만들었더니 겹치는게 잇음. 겹치는 것만 모아서 추상클래스로 부모를 만들어서 사용하면 좀 더 편하다.
보통은 부모가 먼저 있고 자식들이 그걸 이용해서 만드는 건데 반대의 상황이 됨.
추상클래스는 새로운 실체 클래스를 만들기 위한 부모 클래스로만 사용된다. 즉 추상 클래스는 extends뒤에만 올 수있다.

7.10.1 추상클래스 선언

클래스 선언에 abstract키워드를 붙이면 추상클래스 선언이 된다. 추상클래스는 new연산자를 이요해서 객체를 직접만들지 못하고 상속을 통해 자식클래스만 만들수잇다.
public abstract class 클래스명 {필드 생성자 메소드}

7.10.2 추상메소드와 재정의

자식클래스들이 가지고 있는 공통메소드를 뽑아내어 추상클래스로 작성할때 메소드 선언부(리턴타입 메소드명 매개변수)만 동일하고 실행내용은 자식클래스마다 달라야하는 경우가 많다.
예를들어 동물은 소리를 내기때문에 Animal 추상클래스에서 sound()라는 메소드를 선언할때 실행내용은 동물마다 다르기때문에 추상클래스에서 통일하면안된다.
이런경우를 위해 추상클래스는 다음과 같이 추상메소드를 선언할 수있다.
abstract 리턴타입 메소드명(매개변수, ...);

7.11 봉인된 클래스

자바 15부터 추가가되었음.
무분별한 자식클래스 생성을 방지하기 위해서 생김
final로 걸면 자식 못만드는데? 누구는 부모하고 몇개는 안하고 싶을때?
특정만 제외하고 가능하도록 만든것
public sealed Class Person permits Employee, Manager {}
자식은 final을 붙이거나 sealed를 또 붙임. non-sealed로 봉인을 풀어야함.

public sealed class Person permits Employee, Manager {}
public final class Employee extends Person{}
public non-sealed class Manager extends Person {}
public class Director extends Manager{} //etends Person은 불가능