오늘의 학습 키워드

  1. 상속
  2. 오버라이딩
  3. 다형성1
  4. 추상 클래스
  5. 인터페이스
  6. default 메소드 & static 메소드
  7. 다형성2

1. 상속

  • 공통적인 코드 관리 가능
  • 코드의 중복이 제거되고 재사용성이 크게 증가하여 생산성과 유지보수성에 매우 유리
  • public class 자식클래스 extends 부모클래스 {}
  • 클래스 간의 관계: 상속 관계 & 포함 관계
  • 포함 관계 예시 -> 배열을 사용!!
    • 타이어 클래스
public class Tire {
    String company; // 타이어 회사
    double price; // 타이어 가격

    public Tire(String company, double price) {
        this.company = company;
        this.price = price;
    }
}
  • 차문 클래스
public class Door {
    String company; // 차문 회사
    String location; // 차문 위치, FL, FR, BL, BR

    public Door(String company, String location) {
        this.company = company;
        this.location = location;
    }
}
  • 핸들 클래스
public class Handle {
    String company; // 핸들 회사
    String type; // 핸들 타입

    public Handle(String company, String type) {
        this.company = company;
        this.type = type;
    }

    public void turnHandle(String direction) {
        System.out.println(direction + " 방향으로 핸들을 돌립니다.");
    }
}
  • 자동차 클래스
public class Car {

    static final String company = "GENESIS"; // 자동차 회사
    String model; // 자동차 모델
    String color; // 자동차 색상
    double price; // 자동차 가격

    double speed;  // 자동차 속도 , km/h
    char gear = 'P'; // 기어의 상태, P,R,N,D
    boolean lights; // 자동차 조명의 상태

    Tire[] tire; 
    Door[] door; // tire와 door 모두 각각의 클래스 타입의 배열⭐️
    Handle handle;

    public Car(String model, String color, double price) {
        this.model = model;
        this.color = color;
        this.price = price;
    }

    public void setTire(Tire ... tire) {
        this.tire = tire;
    }
    // Tire ... tire -> 가변길이 받을 수 있도록 설정⭐️

    public void setDoor(Door ... door) {
        this.door = door;
    }

    public void setHandle(Handle handle) {
        this.handle = handle;
    }

    public double gasPedal(double kmh, char type) {
        changeGear(type);
        speed = kmh;
        return speed;
    }

    public double brakePedal() {
        speed = 0;
        return speed;
    }

    public char changeGear(char type) {
        gear = type;
        return gear;
    }

    public boolean onOffLights() {
        lights = !lights;
        return lights;
    }

    public void horn() {
        System.out.println("빵빵");
    }
}
  • Main 클래스
public class Main {
    public static void main(String[] args) {
        // 자동차 객체 생성
        Car car = new Car("GV80", "Black", 50000000);

        // 자동차 부품 : 타이어, 차문, 핸들 선언
        Tire[] tires = new Tire[]{
                new Tire("KIA", 150000), new Tire("금호", 150000),
                new Tire("Samsung", 150000), new Tire("LG", 150000)
        }; //Tire 타입의 배열 생성 ⭐️
        Door[] doors = new Door[]{
                new Door("LG", "FL"), new Door("KIA", "FR"),
                new Door("Samsung", "BL"), new Door("LG", "BR")
        };
        Handle handle = new Handle("Samsung", "S");


        // 자동차 객체에 부품 등록
        car.setTire(tires); // tires는 4개의 tire을 가지고 있는 배열, this.tire -> Tire 타입의 배열이므로 들어갈 수 있음⭐️
        car.setDoor(doors);
        car.setHandle(handle);

        // 등록된 부품 확인하기
        for (Tire tire : car.tire) {
            System.out.println("tire.company = " + tire.company);
        }
        System.out.println();

        for (Door door : car.door) {
            System.out.println("door.company = " + door.company);
            System.out.println("door.location = " + door.location);
            System.out.println();
        }
        System.out.println();

        // 자동차 핸들 인스턴스 참조형 변수에 저장
        Handle carHandle = car.handle;
        System.out.println("carHandle.company = " + carHandle.company);
        System.out.println("carHandle.type = " + carHandle.type + "\n");

        // 자동차 핸들 조작해보기
        carHandle.turnHandle("Right");
        carHandle.turnHandle("Left");
    }
}
  • Java는 다중 상속을 허용하지 않음 -> 한 부모 클래스에 여러 자식 클래스는 가능하나 한 자식 클래스가 여러 부모 클래스를 상속받는 것은 불가능
  • final 키워드: 클래스, 메소드에 final 키워드를 지정하면 더 이상 상속할 수 없는 클래스, 더 이상 오버라이딩할 수 없는 메소드가 됨
  • Object: Object 클래스는 Java 내 모든 클래스들의 최상위 부모 클래스

2. 오버라이딩

  • 오버라이딩: 부모 클래스로부터 상속 받은 메소드의 내용을 재정의 하는 것
  • 오버라이딩 조건
    1. 선언부가 부모 클래스의 메소드와 일치해야 한다.
    2. 접근 제어자를 부모 클래스의 메소드보다 좁은 범위로 변경할 수 없다.
    3. 예외는 부모 클래스의 메소드 보다 많이 선언할 수 없다.
  • 예시
    • 부모 클래스의 메소드
    public double brakePedal() {
    	speed = 0;
      return speed;
    }
    • 자식 클래스에서 오버라이딩된 메소드
    @Override
    public double brakePedal(){
    	speed = 100;
      System.out.println("스포츠카에 브레이크란 없다.");
      return speed;
    }
    -> 이처럼 부모 클래스의 메소드를 가져와 {} 구현 블록을 재정의하는 것을 오버라이딩이라고 함
  • super
    - super는 부모 클래스의 멤버를 참조할 수 있는 키워드
    • 예시
      public void setCarInfo(String model, String color, double price) {
      	super.model = model; // model, color은 부모 필드에 set
          super.color = color;
          this.price = price;
       }

3. 다형성1

  • 자동 타입변환: 부모타입 변수 = 자식타입객체;
  • 예시
[부모 클래스]
class Mammal {
    // 포유류는 새끼를 낳고 모유수유를 한다.
    public void feeding() {
        System.out.println("모유수유를 합니다.");
    }
}

[자식 클래스]
class Whale extends Mammal {
    // 고래는 포유류 이면서 바다에 살며 수영이 가능하다.
    public void swimming() {
        System.out.println("수영하다.");
    }

    @Override
    public void feeding() {
        System.out.println("고래는 모유수유를 합니다.");
    }
}

[Main 클래스]
public class Main {
    public static void main(String[] args) {
        // 고래는 포유류이기 때문에 포유류 타입으로 변환될 수 있습니다.
        Mammal mammal = new Whale();✔️

        // 하지만 포유류 전부가 바다에 살고 수영을 할 수 있는 것은 아니기 때문에
        // 수영 하다 메서드는 실행 불가
        // 즉, 부모 클래스에 swimming이 선언되어있지 않아서 사용 불가능합니다.
        // mammal.swimming(); // 오류 발생✔️

        // 반대로 모든 포유류가 전부 고래 처럼 수영이 가능한 것이 아니기 때문에 타입변환이 불가능합니다.
        // 즉, 부모타입의 객체는 자식타입의 변수로 변환될 수 없습니다.⭐️
        // Whale whale = new Mammal(); // 오류 발생

        mammal.feeding();
    }
}

✔️ 이 부분이 자동 형 변환 -> 부모 클래스 타입으로 선언을 해도 자식 클래스의 생성자로 생성 할 수 있음 => 이렇게 생성한 객체 타입은 부모 클래스 타입!
=> 반대는 성립하지 않음!!
✔️ swimming 메소드가 Mammal 클래스에 있는 것은 아니기 때문에 오류 발생

  • 강제 타입변환: 자식타입 변수 = (자식타입) 부모타입객체:
    • 자식타입객체가 부모타입으로 자동 타입변환된 후 다시 자식타입으로 변환될 때만 강제 타입변환이 가능
    • 즉, swimming() 메소드를 사용하기 위해
      Whale whale = (Whale) mammal; -> 이 경우만 가능
  • 즉, 자동 형 변환이 완료된 객체만 강제 형 변환이 가능함
  • 다형성: 여러 가지 형태를 가질 수 있는 능력
    -> 소프트웨어 또한 구성하고 있는 객체를 바꿨을 때 소프트웨어의 실행 성능 및 결과물이 다르게 나올 수 있음
  • 다형성 예시
[Tire: 부모 클래스]
public class Tire {
    String company; // 타이어 회사

    public Tire(String company) {
        this.company = company;
    }

    public void rideComfort() {
        System.out.println(company + " 타이어 승차감은?");
    }
}

[KiaTire: 자식 클래스]
public class KiaTire extends Tire{

    public KiaTire(String company) {
        super(company);
    }

    @Override
    public void rideComfort() {
        System.out.println(super.company + " 타이어 승차감은 " + 60);
    }
}

[HankookTire: 자식 클래스]
public class HankookTire extends Tire {

    public HankookTire(String company) {
        super(company);
    }

    @Override
    public void rideComfort() {
        System.out.println(super.company + " 타이어 승차감은 " + 100);
    }
}

[Car 클래스]
public class Car {
    Tire tire;

    public Car(Tire tire) {
        this.tire = tire;
    }

    Tire getHankookTire() {
        return new HankookTire("HANKOOK");
    }

    Tire getKiaTire() {
        return new KiaTire("KIA");
    }
}

[Main 클래스]
public class Main {
    public static void main(String[] args) {
        // 매개변수 다형성 확인!
        Car car1 = new Car(new KiaTire("KIA"));
        Car car2 = new Car(new HankookTire("HANKOOK"));

        // 반환타입 다형성 확인!
        Tire hankookTire = car1.getHankookTire();
        KiaTire kiaTire = (KiaTire) car2.getKiaTire();

        // 오버라이딩된 메서드 호출
        car1.tire.rideComfort(); // KIA 타이어 승차감은 60
        car2.tire.rideComfort(); // HANKOOK 타이어 승차감은 100
    }
}
  • instanceof: 해당 클래스 객체의 원래 클래스명을 체크하는 명령어
    • 대상 객체 instance of 클래스 이름 -> 응답값은 boolean

4. 추상 클래스

  • 추상 클래스: 미완성된 설계도
  • abstract 키워드를 사용하여 선언
  • public abstract class 추상클래스명 {}
  • 추상 클래스는 자식 클래스에 상속되어 자식 클래스에 의해서만 완성될 수 있음
  • 추상 메소드: 아직 구현되지 않은 미완성된 메소드
  • public abstract class 추상클래스명 {
    abstract 리턴타입 메소드이름(매개변수, ...);
    }
    -> 추상 메소드는 일반적인 메소드와는 다르게 블록{}이 없음
  • 추상 메소드는 extends 키워드를 사용하여 클래스에서 상속됨
  • 상속 받은 클래스에서 추상 클래스의 추상 메소드는 반드시 오버라이딩 되어야 한다.
    • public class 클래스명 extends 추상클래스명 {
      @Override
      public 리턴타입 메소드이름(매개변수, ...) {
      //실행문
      }
      }
  • 여러 클래스들의 공통적인 필드와 메소드를 abstract를 사용하면됨
  • 예시
[BenzCar 클래스]
public class BenzCar extends Car {

    @Override
    public void horn() {
        System.out.println("Benz 빵빵");
    }
}

[AudiCar 클래스]
public class AudiCar extends Car {

    @Override
    public void horn() {
        System.out.println("Audi 빵빵");
    }
}

[ZenesisCar 클래스]
public class ZenesisCar extends Car {

    @Override
    public void horn() {
        System.out.println("Zenesis 빵빵");
    }
}

[Car 추상 클래스]
public abstract class Car {
    String company; // 자동차 회사
    String color; // 자동차 색상
    double speed;  // 자동차 속도 , km/h

    public double gasPedal(double kmh) {
        speed = kmh;
        return speed;
    }

    public double brakePedal() {
        speed = 0;
        return speed;
    }

    public abstract void horn();
}

[Main 클래스]
public class Main {
    public static void main(String[] args) {
        Car car1 = new BenzCar();
        car1.horn();
        System.out.println();

        Car car2 = new AudiCar();
        car2.horn();
        System.out.println();

        Car car3 = new ZenesisCar();
        car3.horn();
    }
}

5. 인터페이스

  • 인터페이스는 두 객체를 연결해주는 다리 역할
  • 상속 관계가 없는 다른 클래스들이 서로 동일한 행위 즉, 메소드를 구현해야할 때 인터페이스는 구현 틀래스 들의 동일한 사용 방법과 행위를 보장해 줄 수 있음
  • 인터페이스는 스팩이 정의된 메소드들의 집합
  • interface 키워드를 사용하여 인터페이스 선언
    • public interface 인터페이스명 {}
    • public, default 접근 제어자 지정 가능
  • 인터페이스 구성
    • 모든 멤버 변수는 public static final -> 생략 가능
    • 모든 메소드는 public abstract -> 생략 가능 (static, default 제외)
    • 생략되는 제어자는 컴파일러가 자동으로 추가
  • implements 키워드를 사용하여 인터페이스 구현
    • public class 클래스명 implements 인터페이스명 {
      @Override
      public 리턴타입 메소드이름(매개변수, ...) {
      // 실행문
      }
      }
    • 인터페이스의 추상 메소드는 구현될 때 반드시 오버라이딩 되어야 함
    • 인터페이스의 추상 메소드를 일부만 구현해야한다면 해당 클래스를 추상 클래스로 변경
  • 인터페이스간의 상속 가능 -> extends 키워드 사용
    • 인터페이스는 다중 상속 가능

6. default 메소드 & static 메소드

  • 디폴트 메소드: 추상 메소드의 기본적인 구현을 제공하는 메소드
    -> 블럭{]이 존재해야함
    -> 추상 메소드가 아니기 때문에 인터페이스의 구현체들에서 필수로 재정의 할 필요는 없음
  • 예시
public class Main implements A {

    @Override
    public void a() {
        System.out.println("A");
    }


    public static void main(String[] args) {
        Main main = new Main();
        main.a();

        // 디폴트 메서드 재정의 없이 바로 사용가능합니다.
        main.aa();
    }
}

interface A {
    void a();
    default void aa() {
        System.out.println("AA");
    }
}
  • static 메소드: 객체 없이 호출이 가능
  • 예시
public class Main implements A {

    @Override
    public void a() {
        System.out.println("A");
    }

    public static void main(String[] args) {
        Main main = new Main();
        main.a();
        main.aa();
        System.out.println();

        // static 메서드 aaa() 호출
        A.aaa();
    }
}

interface A {
    void a();
    default void aa() {
        System.out.println("AA");
    }
    static void aaa() {
        System.out.println("static method");
    }
}

7. 다형성2

  • 자동 타입변환
    • 인터페이스 변수 = 구현객체;
    • 예시
  public class Main {
    public static void main(String[] args) {
        
        // A 인터페이스에 구현체 B 대입
        A a1 = new B();
        
        // A 인터페이스에 구편체 B를 상속받은 C 대입
        A a2 = new C();
        
    }
  }

  interface A { }
  class B implements A {}
  class C extends B {}
  • 강제 타입변환
    • 구현객체타입 변수 = (구현객체타입) 인터페이스변수;
    • 예시
public class Main {
    public static void main(String[] args) {

        // A 인터페이스에 구현체 B 대입
        A a1 = new B();
        a1.a();
        // a1.b(); // 불가능✔️

        System.out.println("\nB 강제 타입변환");
        B b = (B) a1;⭐️
        b.a();
        b.b(); // 강제 타입변환으로 사용 가능
        System.out.println();

        // A 인터페이스에 구편체 B를 상속받은 C 대입
        A a2 = new C();
        a2.a();
        //a2.b(); // 불가능
        //a2.c(); // 불가능

        System.out.println("\nC 강제 타입변환");
        C c = (C) a2;
        c.a();
        c.b(); // 강제 타입변환으로 사용 가능
        c.c(); // 강제 타입변환으로 사용 가능


    }
}

interface A {
    void a();
}
class B implements A {
    @Override
    public void a() {
        System.out.println("B.a()");
    }

    public void b() {
        System.out.println("B.b()");
    }
}
class C extends B {
    public void c() {
        System.out.println("C.c()");
    }
}

✔️ B에서 구현하고 있는 것은 (A로 부터 받아)Override된 a()와 b()
b() -> 여기서 자동 형 변환이 되었을 때 A 인터페이스가 가지고 있지 않음
b()을 사용하려면 강제 형 변환을 통해서 사용할 수 있음

  • 인터페이스의 다형성 예시
[TV: 부모 추상 클래스]
public abstract class Tv {

    private String company; // 티비 회사
    private int channel = 1; // 현재 채널 상태
    private int volume = 0;  // 현재 볼륨 상태
    private boolean power = false; // 현재 전원 상태

    public Tv(String company) {
        this.company = company;
    }

    public void displayPower(String company, boolean power) {
        if(power) {
            System.out.println(company + " Tv 전원이 켜졌습니다.");
        } else {
            System.out.println(company + " Tv 전원이 종료되었습니다.");
        }
    }

    public void displayChannel(int channel) {
        System.out.println("현재 채널은 " + channel);
    }

    public void displayVolume(int volume) {
        System.out.println("현재 볼륨은 " + volume);
    }

    public String getCompany() {
        return company;
    }

    public int getChannel() {
        return channel;
    }

    public int getVolume() {
        return volume;
    }

    public boolean isPower() {
        return power;
    }

    public void setChannel(int channel) {
        this.channel = Math.max(channel, 0);
    }

    public void setVolume(int volume) {
        this.volume = Math.max(volume, 0);
    }

    public void setPower(boolean power) {
        this.power = power;
    }
}

[SamsungTv: 자식 클래스]
public class SamsungTv extends Tv implements MultiRemoteController{

    public SamsungTv(String company) {
        super(company);
    }

    @Override
    public void turnOnOff() {
        setPower(!isPower());
        displayPower(getCompany(), isPower());
    }

    @Override
    public void channelUp() {
        setChannel(getChannel() + 1);
        displayChannel(getChannel());
    }

    @Override
    public void channelDown() {
        setChannel(getChannel() - 1);
        displayChannel(getChannel());
    }

    @Override
    public void volumeUp() {
        setVolume(getVolume() + 1);
        displayVolume(getVolume());
    }

    @Override
    public void volumeDown() {
        setVolume(getVolume() - 1);
        displayVolume(getVolume());
    }
}

[LgTv: 자식 클래스]
public class LgTv extends Tv implements MultiRemoteController {

    public LgTv(String company) {
        super(company);
    }

    @Override
    public void turnOnOff() {
        setPower(!isPower());
        displayPower(getCompany(), isPower());
    }

    @Override
    public void channelUp() {
        setChannel(getChannel() + 1);
        displayChannel(getChannel());
    }

    @Override
    public void channelDown() {
        setChannel(getChannel() - 1);
        displayChannel(getChannel());
    }

    @Override
    public void volumeUp() {
        setVolume(getVolume() + 1);
        displayVolume(getVolume());
    }

    @Override
    public void volumeDown() {
        setVolume(getVolume() - 1);
        displayVolume(getVolume());
    }
}

[MultiRemoteController 인터페이스]
public interface MultiRemoteController {

    void turnOnOff();
    void channelUp();
    void channelDown();
    void volumeUp();
    void volumeDown();

    // 매개변수와 반환타입 다형성 확인 메서드
    default MultiRemoteController getTV(Tv tv) {
        if(tv instanceof SamsungTv) {
            return (SamsungTv) tv;
        } else if(tv instanceof LgTv){
            return (LgTv) tv;
        } else {
            throw new NullPointerException("일치하는 Tv 없음");
        }
    }

}

[Main 클래스]
public class Main {
    public static void main(String[] args) {

        // LG TV 구현체를 조작
        MultiRemoteController mrc = new LgTv("LG");✔️
        mrc.turnOnOff();
        mrc.volumeUp();
        mrc.channelDown();
        mrc.channelUp();
        mrc.turnOnOff();

        // 조작 대상을 Samsung TV로 교체
        System.out.println("\n<Samsung TV로 교체>");
        mrc = new SamsungTv("Samsung");
        mrc.turnOnOff();
        mrc.channelUp();
        mrc.volumeDown();
        mrc.volumeUp();
        mrc.turnOnOff();

        // 매개변수, 반환타입 다형성 체크
        System.out.println("\n<매개변수, 반환타입 다형성 체크>");

        MultiRemoteController samsung = mrc.getTV(new SamsungTv("Samsung"));
        samsung.turnOnOff();

        SamsungTv samsungTv = (SamsungTv) samsung;
        samsungTv.turnOnOff();


        System.out.println();
        MultiRemoteController lg = mrc.getTV(new LgTv("LG"));
        lg.turnOnOff();

        LgTv lgTv = (LgTv) lg;
        lgTv.turnOnOff();

    }
}

✔️ MultiRemoteController 타입으로 mrc를 선언해야 LgTv, SamsungTv 객체로 교체 가능

profile
안녕하세요

0개의 댓글