til(11_13)

김태은·2024년 11월 13일

객체 지향



상속

상속은 오버라딩과 항상 공존

클래스간의 관계와 상속

클래스간의 관계와 상속

  • 부모클래스의 필드와 메서드를 자식 클래스에게 물려줌 < - 공통적인 코드 관리가 편함 +재사용성이 가능하여 유지보수에 좋음
  • 상속은 확장으로 부모클래스가 더 큰 것이 아니라 물려받은 자식 클래스은 부모 클래스의 베이스 + 어떤 것이 추가되기에 자식클래스가 더 큰 바운더리를 가진다.
  • 자식 클래스에 어떤 필드 메서드가 추가되어도 부모클래스에는 영향이 가지 않는다.

extends 를 사용하여 클래스간의 상속을 정의

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

클래스간의 관계

  • 상속관계 : is - a (”~은 ~(이)다”) ex 고래는 포유류이다 o // 고래는 포유류를 가지고 있다. x
  • 포함관계 : has - a (”~은 ~을(를) 가지고 있다”) - 여러가지도 가능!
    ps 자동차에 바퀴가 4개 문이4개

단일 상속과 다중 상속

자바는 다중 상속을 허용하지않는다.( 한 부모가 여러 자식을 가지는 것은 가능 , 한 자식이 여러 부모를 가질 수 없다. ) - 관계가 복잡해 지기에


final - 상속

부모 클래스에 final 키워드를 지정하여 선언하면 최종 클래스가 됨으로 더 이상 상속할 수 없는 클래스 된다.


Object

Object 클래스는 Java 내 모든 클래스들의 최상위 부모 클래스이다.
ps Object는 클래스 이다.
모든 클래스는 Object의 메서드를 사용할 수 있다. < - 최상위 클래스여서


오버라이딩

오버라이딩
부모 클래스로부터 상속받은 메서드의 내용을 재정의 하는 것을 말한다. < - 부모에게 받은 메서드들 자식 클래스에 입맛에 맞게!

오버라이딩의 조건

  • 선언부가 부모 클래스의 메서드와 일치해야 한다. < - 선언부에서 다르면 새로 만든걸로 인지한다.
  • 접근 제어자를 부모 클래스의 메서드 보다 좁은 범위로 변경할 수 없다. < - 더 넓은 범위로는 변경 가능!
  • 예외는 부모 클래스의 메서드 보다 많이 선언할 수 없다.

super 와 super()

super 와 super() 는 this 와 this() 와 닮은 거보니! 인스터스(소괄호 없는) 와 생성자(소괄호 있는)랑 관련이 있다는 걸 알 수 있을 것이다!

super
부모클래스의 맴버를 참조할 수 있는 키워드이다. < - 인스턴스를 했을 때, 부모를 지칭할 수 있는것 이라 생각하자 < - > 나 자신을 지칭하는 건 this

super를 이용하면 자식 객체의 필드 값이 변하는게 아닌 부모 객체의 필드값에 영향을 준다

// 부모 클래스 Car
String model; // 자동차 모델 < - 밑에 코드에 의해 건드림 당하는곳
String color; // 자동차 색상 < - 밑에 코드에 의해 건드림 당하는곳
double price; // 자동차 가격

// 자식 클래스 SportsCar
String model = "Ferrari"; // 자동차 모델
String color = "Red"; // 자동차 색상
double price = 300000000; // 자동차 가격

public void setCarInfo(String model, String color, double price) {
    super.model = model; // SportsCar(자식) 인스턴스가 부모 인스턴스 Car에 필드 model을 지칭하는 것으로 보면 된다. model은 부모 필드에 set 
    super.color = color; // color는 부모 필드에 set
    this.price = price; // 말그대로 SportsCar(자식) 인스턴스 자기자신을 지칭하는 것, price는 자식 필드에 set
}

다형성

참조변수의 타입변환

  • 자동 타입변환

    // 부모
    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(); // ⚠️ Whale의 생성자로 인스터스를 만들 때, 부모 클래스를 이용해서 타입을 정해줘도 된다. - > 고래 ->> 포유류 로 형변환 -메모 3-15
    	
           // 하지만 포유류 전부가 바다에 살고 수영을 할 수 있는 것은 아니기 때문에
           // 수영 하다 메서드는 실행 불가
           // 즉, 부모 클래스에 swimming이 선언되어있지 않아서 사용 불가능하다.
           // mammal.swimming(); // 오류 발생 
           //- > 자식 객체에 있는 새로운 메서드를 mammal 클래스에는 없는 메서드! - > Whale로 생성을 했어도 해당 인스턴스는 Mammal의 틀에 있다? 라고 생각하자
    
           // 반대로 모든 포유류가 전부 고래 처럼 수영이 가능한 것이 아니기 때문에 타입변환이 불가능합다.
           // 즉, 부모타입의 객체는 자식타입의 변수로 변환될 수 없다. 1. 이라면
           // Whale whale = new Mammal(); // 오류 발생 - > 작은 그릇에 큰 그릇을 넣는
    
           mammal.feeding(); // < - ⚠️ 결국 생성된 인스턴스는 포유류이다.
       }
    }
  • 강제 타입변환

⚠️ 강제 타입변환의 조건
자식 생성자를 이용하여 부모의 클래스 인스턴스를 만들었을 때, 자식 클래스의 맴버를 사용하고 싶을 수 있다! - > 이때만! 사용 가능!!
ps 자동 형변환이 완료된 애들만 다시 돌아갈 수 있다. <- 메모 3-15

public class Main {
    public static void main(String[] args) {
        // 자동 타입변환된 부모타입의 변수의 자식 객체
        Mammal mammal = new Whale(); // < - _**조건**_
        mammal.feeding();

        // 자식객체 고래의 수영 기능을 사용하고 싶다면
        // 다시 자식타입으로 강제 타입변환을 하면된다.
        Whale whale = (Whale) mammal; // < - 이런 식으로 가능! 하지만 조건이 있다.
        // 단, Whale whale = new Mammal(); < - 의 형태은 안된다!!
        whale.swimming();

        Mammal newMammal = new Mammal();
        // Whale newWhale = (Whale) newMammal;
    }
}

다형성
여러가지 형태를 가질 수 있는 능력을 말한다.
다른 객체를 맴버로 받아들이는 것 - > 참조변수 와 타입 변환을 이용한다.

// 부모
public class Car {
    Tire tire;

    public Car(Tire tire) { < - 1). 생성자에서 타이어를 넣어야 한다
        this.tire = tire;
    }

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

    Tire getKiaTire() {
        return new KiaTire("KIA");
    }
}
// 자식
public class KiaTire extends Tire{

    public KiaTire(String company) { - > 2).
        super(company);
    }

    @Override
    public void rideComfort() {
        System.out.println(super.company + " 타이어 승차감은 " + 60);
    }
}
// main
public class Main {
    public static void main(String[] args) {
        // 매개변수 다형성 확인!
        Car car1 = new Car(new KiaTire("KIA")); // < - 1). 과 다르게 호출! -> 2). 으로!
        // - > 가능한 이유는 자동 타입변환이 가능해서 < - KiaTire 클래스의 인스턴스를 넣었다! 라고 생각하자 
        // ⚠️ 매개변수 자리엔 타입과 변수 
        Car car2 = new Car(new HankookTire("HANKOOK")); // < - 1). 과 다르게 호출!

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

        // 오버라이딩된 메서드 호출
        car1.tire.rideComfort(); // KIA 타이어 승차감은 60
        car2.tire.rideComfort(); // HANKOOK 타이어 승차감은 100
    }
}

instance of

instance of
다형성 기능에 의해 해당 클래스 객체의 원래 클래스 명을 체크하는 것이 필요할 때가 있다.
ps 보면 알겠지만, 다형성이랍시고, 난발하면 헛갈릿 것이 분명하기에

instance of 의 사용
{대상 객체 - 궁금한 } instance of {클래스 이름 - 추측하는 것}

⚠️ instance of 의 이상한 점은 클래스의 상관관계의 트리가 있다고 생각했을 때, 포함 관계면 결과값이 true가 나온다. 또한 인스턴스화 할 때, 생성자 클래스명과 생성된 인스턴스의 클래스가 다를 경우, 두 클래스 모두 true 값이 나온다... ? 이러면 instance of 의 이유를 탄생이유를 줄이는 행동 같은데... 그냥 인스턴스가 많아지면 어디서 온건지 헛갈려서 그런건가? -메모 3-15


추상클래스

추상클래스 ( -메모 인터페이스와 비슷한 점이 많다.)

  • 미완성된 클래스이다. - > '텅빈(맴버) 부모 클래스을 상속받은 자식 클래스가 이를 자기 입맛대로 완성해나가는 것' 의 서순을 바꾸어 맴버가 빵빵한 예비 자식 클래스들이 부모 클래스를 만들고 싶어! 해서 자신들의 공통된 맴버를 가지고 만든 부모 클래스( < - 추상 클래스 )

  • 추상 클래스는 추상 메서드를 포함할 수 있다.

    추상 메서드가 없어도 추상클래스로 선언할 수 있다.

  • 추상 클래스는 자식클래스에 상속되어 자식 클래스에 의해서만 완성될 수 있다.

  • 추상 클래스는 여러개의 자식 클래스들에서 공통적인 필드나 메서드를 추출해서 만들 수 있다.

생각할 적에 맴버에서 메서드 정도만 생각하면 될 듯하다.

추상 클래스 사용법
abstract 키워드를 이용하여 추상클래스로 선언할 수 있다.

public abstract class 추상클래스명 {

}

추상 메서드 사용법

public abstract class 추상클래스명 {
		abstract 리턴타입 메서드이름(매개변수, ...); // < - '{}( < - 구현부분)' 가 없다
        // 정의만 할뿐 실행 내용이 없다!
}

상속 받은 클래스 에서 추상 클래스의 추상 메서드는 반드시 오버라이딩 되어야한다. -메모 추상클래스와 인터페이스의 다른점

// 같은 부분 1)  // 다른 부분 2)

// 예비 자식 1
public class BenzCar {
    String company; // 자동차 회사 : GENESIS
    String color; // 자동차 색상
    double speed;  // 자동차 속도 , km/h

    public double gasPedal(double kmh) { // < - 1) 
        speed = kmh;
        return speed;
    }

    public double brakePedal() { // < - 1) 
        speed = 0;
        return speed;
    }

    public void horn() { // < - 2) 
        System.out.println("Benz 빵빵");
    }

}

// 예비 자식 2
public class AudiCar {
    String company; // 자동차 회사 : GENESIS
    String color; // 자동차 색상
    double speed;  // 자동차 속도 , km/h

    public double gasPedal(double kmh) { // < - 1) 
        speed = kmh;
        return speed;
    }

    public double brakePedal() { // < - 1) 
        speed = 0;
        return speed;
    }

    public void horn() { // < - 2) 
        System.out.println("Audi 빵빵");
    }

}

// 예비 자식 3
public class ZenesisCar {
    String company; // 자동차 회사 : GENESIS
    String color; // 자동차 색상
    double speed;  // 자동차 속도 , km/h
    
    public double gasPedal(double kmh) { // < - 1) 
        speed = kmh;
        return speed;
    }

    public double brakePedal() { // < - 1) 
        speed = 0;
        return speed;
    }

    public void horn() { // < - 2) 
        System.out.println("Zenesis 빵빵");
    }

}

// 추상 클래스 - 예비 부모
public abstract class Car {
    String company; // 자동차 회사 : GENESIS
    String color; // 자동차 색상
    double speed;  // 자동차 속도 , km/h

    public double gasPedal(double kmh) { // < - 1) 빼다 박음
        speed = kmh;
        return speed;
    }

    public double brakePedal() { // < - 1) 빼다 박음
        speed = 0;
        return speed;
    }

    public abstract void horn(); // < - 2) - > 세개의 자동차(예비 자식들)이 오버라이딩해야하는 부분
    
    // 우선 공통된 부분이 아니면 추상으로 넘기고, 공통된 다면 공통된 부분을 입력
    // 이래야 나중에 해당 추상 클래스로 다른 자식 친구를 만들어 줄 수 있기 때문에
    
    // 한마디로 그냥 부모 - > 자식 의 서순만 바꿔어 놓고 , 만들어진 추상 부모로 부모 - > 자식 생성
}


인터페이스

인터페이스

얼굴 사이에 있는 것 - > 매개체의 역할을 할 것이다.

  • 두 객체를 이어주는 다리 역할을 해준다. ex 리모콘을 이용하여 tv를 켠다 < - API와 비슷한 역할을 하지 안을까? 라는 생각을 한다.
  • 메서드( < - 상속 관계가 없는 다른 클래스들이 서로 동일한 행위를 해야할 때 )를 구현할 때 , 인터페이스는 구현 클래스들의 동일한 사용 방법과 행위를 보장해 줄 수 있다. -메모 3-17

interface 사용법
interface 키워드 사용

public interface 인터페이스명 {  // < - 클래스명 대신 interface명

}

interface 맴버

  • 모든 맴버변수는 public static final 이다. - > '상수'다
  • 모든 메서드는 public abstract 이다
    ps public static final 혹은 public abstract 을 하지 않아도 컴파일러가 자동으로 추가해줌
  • 메서드에서 static 메서드와 default는 생략이 안된다. -메모 3-17

interface 구현

  • 추상클래스와 마찬가지로 직접 생성(인스턴스) 불가하여, 구연체(클래스)가 있어야 한다.
  • 인터페이스의 추상 메서드는 구현될 때, 반드시 오버라이딩 되어야한다. - > 추상클래스와 비슷하게 추상 메서드를 가지고, 메서드의 실행문이 텅빈 상태라고 생각하면 될 듯하다.
    ps 만약 추상 메서드를 일부만 구현해야 한다면 해당 클래스를 추상 클래스로 변경해주면 된다. -메모 3-17

implements를 받아서 인터페이스를 구현할 수 있다.

public class 클래스명 implements 인터페이스명 { 
			// 클래스에서 인터페이스 implements를 받아서 추상 메서드를 오버라이딩하는 것 -메모 3-17
			// 추상 메서드 오버라이딩
			@Override
	    public 리턴타입 메서드이름(매개변수, ...) {
			       // 실행문
	    }
}

interface의 상속
인터페이스 간의 상속이 가능 하다. 이때의 키워드는 extends 이다.

인터페이스는 다중 상속이 가능하다.

public class Main implements C {

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

    @Override
    public void b() {
				System.out.println("B");
    }
}
// 인터페이스 선언  - > 생성(구현)이 아님
interface A {
    void a();
}
interface B {
    void b();
}
interface C extends A, B { } // - > A B 가 둘다 부모!

디폴트 와 static

디폴트 메서드
추상 메서드의 기본적인 구현을 제공하는 메서드이다. -메모 3-18 Q. 그냥 부속품이라고 생각하면 되는걸까?

  • 메서드명 앞에 default 키워드를 붙이고, 블록{} 을 가진다.

    디폴트는 추상메서드가 아니기 때문에(추상 메서드는 {} 가 애초에 없다!) 인터페이스의 구현체들에서 필수로 재정의 할 필요는 없다. - > 오버라이딩을 할 필요가 없다는 뜻

  • 접근 제어자는 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();

        // 디폴트 메서드 재정의 없이 바로 사용가능하다.- 오버라이딩 하지 않고 사용 가능하다는 뜻
        main.aa();
    }
}

interface A {
    void a();
    default void aa() { // < - default // public 생략
        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(); //  2).                       
        System.out.println();

        // static 메서드 aaa() 호출
        A.aaa(); // < - 생성자를 통해, 인스턴스 만드는 과정이 없이! 바로 메서드 호출!
        // 이유 = 1).
    }
}

interface A {
    void a();
    default void aa() { // 2). static 이 아닌 default - > 인스턴스를 만들고 함수를 호출!
        System.out.println("AA");
    }
    static void aaa() { // < - 1).
        System.out.println("static method");
    }
}

다형성

자동 타입변환

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 {} // 구현체 B // 인터페이스 A
class C extends B {} // 부모 B // 자식 C

강제 타입변환

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

        // A 인터페이스에 구현체 B 대입
        A a1 = new B(); // < - 자동 형변환
        a1.a(); // - > interface A에 a메서드를 오버라이딩한 클래스 B에 a 메서드를 호출! 
        
        ⚠️//Q. 상속의 다형성 공부 할 땐, 클래스 A 의 맴버인 메서드를 사용했는데...? ~
        // ~ 그럼 클래스 A(부모)로 만들어진 인스턴스에서 메서드를 실행하면, 생성자 클래스(자식)에 오버라이딩 된 함수를 실행하는 건지?
        
        
        ⚠️// A. _**우선 인스턴스가 만들어 졌을때, 생성자 기준으로 메서드가 작동 한다**_는 걸 알아야한다! 
        // 이 부분에서 헛갈렷는데... 사용가능한 메서드를 타입이에서 정해주는 것이 아니다.  ( - 부모 클래스 혹은 인터페이스가 아니다!) , 타입은 말그래로 타입 범위로 생각하자!
        // - > 즉, 자식 클래스의 눈높이가 부모 클레스로 바뀐다! 
        // - >> 위 예제로 보면 B 는 a b 메서드가 있고, A 는 a 메서드만이 존재한다. 
        // * _**저장 변수 타입을 A로 지정해주면 A가 보는 관점의 메서드! **_* 
        //"난 a 메서드 밖에 몰라 뿌엥~" - > 그래서 a1객체는 a메서드만을 사용할 수 있고, 말 그대로 사용가능 메서드 범위를 알려준거기 때문에 
        // >> B 에 a 메서드가 실행된다!
        
        // a1.b(); // 불가능 // < - A 인터페이스를 가지고있지 않음.

        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()");
    }
}

⚠️ 위에 궁금증은 해소는 클래스 간의 상속 - 다형성 과도 같다.



회고

오늘은 자바 객체지향 강의를 마무리 하였다. 많은 부분에서 모르는 것이 많아 메모가 수두룩하다. 하나하나 지워야 할텐데, 공부하고 이해했던 내용이 메모로나 채워지지 않게 우선 복습을 해봐야겠다.

ps 오늘은 깃허브와 인텔리제이 연동 , 깃허브와 잔디 연동 을 해보았다.
잔디와의 연동은 레포지토리만 입력하면 되서 연동이 쉬웠지만, 깃허브와 인텔리제이 연동에서 많이 애를 먹었던거 같다.

우선 깃허브를 레포지토리 생성할 때, readme를 체크한 것이 문제가 되었다. 그래서 인텔리제이 에서 올린 파일이 깃허브 main 에 들어가지 않고 새로운 브랜치를 만들어 데이터를 저장하였다. 이 문제를 해결하기 위해 인텔리제이에 브랜치 이름을 바꿔보기도 하고, 레포지토리를 새로 만들어 보기도 했지만 문제는 해결되지않았다. 결국 난 영빈's gpt에게 도움을 청했고, 영빈's gpt 왈 ' 시작점이 다른 것이 하나로 합쳐지려 해서 문제가 발생!' allow 명령어를 통해 main으로 합칠 수 있었다...

객체지향도 어렵지만, 깃허브도 나에겐 아직 너무 어렵다.

profile
Spring_4

0개의 댓글