새소식

TIL

[TIL] 240425 <자바> 상속,인터페이스

  • -

 

 

public class 자식클래스 extends 부모클래스 {

}
  1. 부모 클래스에 새로운 필드와 메서드가 추가되면, 자식 클래스는 이를 상속받아 사용 가능.
  2. 자식 클래스에 새로운 필드와 메서드가 추가되어도, 부모 클래스에 영향X!
  3. 따라서 자식 클래스의 멤버 개수는 부모 클래스보다 항상 같거나 많음.

 

 

자바는 다중 상속 허용하지 않음!!! (하나의 자식에 대한 부모가 여러개 X!!!)

: 다중 상속을 허용하면 클래스 간의 관계가 복잡해짐

  • 만약 자식 클래스에서 상속받는 서로 다른 부모 클래스들이 같은 이름의 멤버를 가지고 있다면?
    → 자식 클래스에서는 이 멤버를 구별할 수 있는 방법이 없다!

 

 

 

final 클래스와 메소드는 상속 불가!

  • 클래스에 final 키워드를 지정하여 선언하면 최종적인 클래스가 되므로 더 이상 상속불가!!!
  • 메서드에 final 키워드를 지정하여 선언하면 최종적인 메서드가 됨으로 더 이상 오버라이딩불가!!!

 

 

 

Object 클래스

  • Java 내 모든 클래스들의 최상위 부모 클래스
  • 따라서, 모든 클래스는 Object의 메서드를 사용 가능
  • 부모 클래스가 없는 자식 클래스는 컴파일러에 의해 자동으로 Object 클래스를 상속받음.
    •  
    • Object clone() : 해당 객체의 복제본을 생성하여 반환
    • boolean equals(Object object) : 해당 객체와 전달받은 객체가 같은지 여부를 반환
    • Class getClass() : 해당 객체의 클래스 타입을 반환
    • int hashCode() : 자바에서 객체를 식별하는 정수값인 해시 코드를 반환
    • String toString() : 해당 객체의 정보를 문자열로 반환. & Object 클래스에서는 클래스이름 @해쉬코드값 반환.

 

 

 

상속관계 vs 포함관계

  • 상속관계 : is - a (”~은 ~(이)다”)
  • 포함관계 : has - a (”~은 ~을(를) 가지고 있다”)

    <포함관계> : Car 클래스는 Tire, Door, Handle 클래스를 포함하고 있음!

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; // 자동차 조명의 상태


/* Car 클래스는 Tire,Door,Handle클래스를 포함하고 있음!! */
    Tire[] tire;  //배열
    Door[] 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;
    }

    public void setDoor(Door ... door) {  //가변 매개변수
        this.door = door;
    }

    public void setHandle(Handle handle) {
        this.handle = handle;
    }
    
	. . .
}
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)
        };
        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);
        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");
    }
}

 

 


 

오버라이딩

부모 클래스로부터 상속받은 메서드의 내용을 재정의 하는 것

public class SportsCar extends Car{
    String engine;
    public void booster() {
        System.out.println("엔진 " + engine + " 부앙~\n");
    }

    public SportsCar(String engine) {
        this.engine = engine;
    }


    @Override	//어노테이션
    public double brakePedal() {
        speed = 100;
        System.out.println("스포츠카에 브레이크란 없다");
        return speed;
    }

    @Override	//어노테이션
    public void horn() {
        booster();
    }
}

 

 

 

super : 부모 클래스의 멤버 참조가능

super(...) : 부모 클래스의 생성자 호출

// 자식 클래스 SportsCar 생성자
public SportsCar(String model, String color, double price, String engine) {
     // this.engine = engine; // 오류 발생
    super(model, color, price);  //부모클래스 생성자 호출
    this.engine = engine;
}

 

 


 

 

<참조변수의 타입변환>

1. 자동 타입 변환 : 부모 타입 변수 = 자식 타입 객체;

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

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

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

        mammal.feeding();
    }
}

 

 

2. 강제 타입 변환 : 자식 타입 변수 = (자식 타입) 부모 타입 객체;

  • 자식 타입 객체가 부모 타입으로 자동 타입 변환된 후, 다시 자식 타입으로 변환될 때만 강제 타입 변환이 가능!
  • 부모 타입 변수로는 자식 타입 객체의 고유한 멤버를 사용할 수 없기 때문에 사용이 필요한 경우가 생겼을 때 강제 타입 변환을 사용함.
// 자식타입객체가 자동 타입변환된 부모타입의 변수
Mammal mammal = new Whale();
mammal.feeding();

// 자식객체 고래의 수영 기능을 사용하고 싶다면
// 다시 자식타입으로 강제 타입변환을 하면된다.
Whale whale = (Whale) mammal;
whale.swimming();

 

 

 

다형성

여러 가지 형태를 가질 수 있는 능력

public class Car {

//Tire : 부모 클래스
    Tire tire;

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

//HankookTire : 자식클래스
    Tire getHankookTire() {
        return new HankookTire("HANKOOK");
    }

//KiaTire : 자식클래스
    Tire getKiaTire() {
        return new KiaTire("KIA");
    }
}
public class Main {
    public static void main(String[] args) {
    
        // '매개변수' 다형성 확인!
        //Car생성자에서 매개변수 타입이 부모 타이어이기 때문에
        //자식 타이어 객체들을 매개값으로 전달가능
        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
// 다형성

class Parent { }
class Child extends Parent { }
class Brother extends Parent { }


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


        Parent p = new Parent();

        System.out.println(p instanceof Object); // true 출력
        System.out.println(p instanceof Parent); // true 출력
        System.out.println(p instanceof Child);  // false 출력
        

        Parent c = new Child();

        System.out.println(c instanceof Object); // true 출력
        System.out.println(c instanceof Parent); // true 출력
        System.out.println(c instanceof Child);  // true 출력

    }
}

 

 


 

추상 클래스

public abstract class 추상클래스명 {
		abstract 리턴타입 메서드이름(매개변수, ...);
}
  • 추상 메서드를 포함가능 (없어도됨)
    - 추상 메서드는 일반적인 메서드와는 다르게 블록{ }이 없음!!!! (내용없음!)
  • 추상 클래스는 자식 클래스에 상속되어 자식 클래스에 의해서만 완성가능
  • 여러 개의 자식 클래스들에서 공통적인 필드나 메서드를 추출해서 생성가능

 

 

<추상 클래스를 상속 시>

상속받은 클래스에서 추상 클래스의 추상 메서드는 반드시 오버라이딩 되어야함!

public class 클래스명 extends 추상클래스명 {

	@Override
    public 리턴타입 메서드이름(매개변수, ...) {
		       // 실행문
    }
}

 

 

 


 

 

상속 관계가 없는 다른 클래스들이 서로 동일한 행위 즉, 메서드를 구현해야 할 때
인터페이스는 구현 클래스들의 동일한 사용 방법과 행위를 보장가능

  • 인터페이스는 스펙이 정의된 메서드들의 집합
  • 인터페이스의 구현 클래스들은 반드시 정의된 메서드들을 구현해야함.
  • 이러한 특징은 인터페이스에 다형성을 적용할 수 있게해줌
  • 인터페이스는 클래스와 마찬가지로 public, default 접근 제어자를 지정가능
public interface 인터페이스명 { 
	public static final char A = 'A';
    static char B = 'B';
    final char C = 'C';
    char D = 'D';

    void turnOn(); // public abstract void turnOn();
}
  • 모든 멤버 변수는 public static final이어야 함 (생략가능)
  • 모든 메서드는 public abstract이어야 함 (생략가능 but static 메서드와 default 메서드 예외)
  • 생략되는 제어자는 컴파일러가 자동으로 추가해줍니다.

 

 

<인터페이스 구현>

추상 클래스와 마찬가지로 직접 인스턴스를 생성할 수 없기 때문에 클래스에 구현되어 생성됨

public class 클래스명 implements 인터페이스명 { 
		// 추상 메서드 오버라이딩
		@Override
	    public 리턴타입 메서드이름(매개변수, ...) {
			       // 실행문
	    }
}
  • 인터페이스의 추상 메서드는 구현될 때 반드시 오버라이딩 되어야 함!!!
  • 만약 인터페이스의 추상 메서드를 일부만 구현해야 한다면, 해당 클래스를 추상 클래스로 변경해주면됨

 

 

<인터페이스 간 상속>

  • 인터페이스 간의 상속은 implements가 아니라 extends 키워드 사용
  • 인터페이스는 클래스와는 다르게 다중 상속 가능
public class Main extends D implements C {  //D클래스 상속 & C 인터페이스

    @Override  //인터페이스의 추상 메서드는 구현될 때 반드시 오버라이딩되어야함!!!
    public void a() {
        System.out.println("A");
    }

    @Override  //인터페이스의 추상 메서드는 구현될 때 반드시 오버라이딩되어야함!!!
    public void b() {
        System.out.println("B");
    }


    @Override  //오버라이딩
    void d() {
        super.d();
    }

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



//인터페이스
interface A {
    void a();  //인터페이스의 모든 메서드는 public abstract
}

interface B {
    void b();  //인터페이스의 모든 메서드는 public abstract
}

interface C extends A, B {  //인터페이스A,B 다중상속받음
}



class D {
    void d() {
        System.out.println("D");
    }
}

 

 

 

default 메소드

추상 메서드의 기본적인 구현을 제공

  • 블럭{ }이 존재해야함
  • 접근 제어자가 public이며 생략이 가능
  • 추상 메서드가 아니기 때문에 인터페이스의 구현체들에서 필수로 재정의 할 필요 없음 (오버라이드할 필요X!)

 

static 메소드

인터페이스에서 static 메서드 선언 가능

  • static의 특성 그대로. 인터페이스의 static 메서드 또한 객체 없이 호출 가능
  • 접근 제어자를 생략하면 컴파일러가 public을 추가해줌

 

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();
        
        // default 메서드의 재정의 없이 바로 사용가능
        main.aa();
        System.out.println();

        // static 메서드 aaa() 호출
        //객체 없이 호출 가능!!!!!!
        A.aaa();
    }
}

interface A {
    void a();  //인터페이스의 모든 메서드는 추상메서드
    
    default void aa() {  //default 메서드
        System.out.println("AA");
    }
    
    static void aaa() {  //static 메서드
        System.out.println("static method");
    }
}

 

 

 


 

<인터페이스의 타입변환>

1. 자동 타입 변환 : 인터페이스 변수 = 구현객체;

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 {}

 

2. 강제 타입 변환 : 구현 객체 타입 변수 = (구현 객체 타입) 인터페이스 변수;

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

        // A 인터페이스에 구현체 B 대입
        A a1 = new B();
        a1.a();
        //a1은 인터페이스 A타입이기 때문에(자동 형변환) a() 메서드만 가지고 있음
        // a1.b(); // 불가능 : B가 A로 형변환될 때 B에만 있는 b()메서드는 없음
        

        System.out.println("\nB 강제 타입변환");
        B b = (B) a1;
        b.a();
        b.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()");
    }
}

 

 

 

 

<인터페이스의 다형성>

 

(부모 추상클래스) 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;
    }
}

 

 

(자식클래스1) 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());
    }
}

 

 

(자식클래스2) 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로 교체
        //TV 구현 객체를 교체해도 멀티 리모컨 인터페이스 변수는 전혀 수정 작업 없이 
		//그대로 기능 호출가능
        System.out.println("\n<Samsung TV로 교체>");
        mrc = new SamsungTv("Samsung");
        mrc.turnOnOff();
        mrc.channelUp();
        mrc.volumeDown();
        mrc.volumeUp();
        mrc.turnOnOff();
        
        //멀티 리모컨으로 티비를 사용하는 방법은 동일하지만 
        //어떤 TV 구현 객체가 대입되었느냐에 따라 실행 결과가 다르게 나옴을 통해 
        //다형성이 적용되었음을 확인가능
        
        




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

    	//1. 인터페이스의 default 메서드 getTV의 매개변수로 Tv가 들어와야하는데 
        //   여기서 SamsungTv인스턴스생성하므로
        //   SamsungTv가 Tv로 자동 형변환되어 getTV의 매개변수로 들어가게됨
        //2. getTV의 리턴값은 SamsungTv인데 인터페이스로 자동 형변환된 samsung
        MultiRemoteController samsung = mrc.getTV(new SamsungTv("Samsung"));
        samsung.turnOnOff();


		//인터페이스 samsung을 다시 강제 형변환하여 SamsungTv가 됨.
        SamsungTv samsungTv = (SamsungTv) samsung;
        samsungTv.turnOnOff();





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

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

    }
}
  • 멀티 리모컨 인터페이스 변수 = TV 구현 객체;
    - TV 구현 객체를 교체해도 멀티 리모컨 인터페이스 변수는 전혀 수정 작업 없이 그대로 기능을 호출가능
    - 멀티 리모컨으로 티비를 사용하는 방법은 동일하지만, 어떤 TV 구현 객체가 대입되었느냐에 따라
      실행 결과가 다르게 나옴을 통해 다형성이 적용되었음을 확인
  • 인터페이스도 마찬가지로 매개변수와 반환 타입에서 다형성 적용가능
    - 위 예제는 반환 타입에는 인터페이스, 매개변수에는 추상 클래스로 다형성이 적용되어있습니다.
    • 인터페이스의 default 메서드

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.