본문 바로가기

WINK-(Web & App)/JAVA 스터디

[2024-2 Java 스터디] 강보경 #4주차

반응형

상속

상속이란 자식 클래스가 부모 클래스의 기능을 그대로 물려받는 기능으로 extends 키워드를 사용한다

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

class Duck extends Animal {  // Animal 클래스를 상속한다.
}

public class Sample {
    public static void main(String[] args) {
        Duck duck = new Duck();
        dog.setName("duckling");
        System.out.println(duck.name);  // duckling
    }
}

 

Duck 클래스에 객체 변수인 name과 메서드인 setName을 만들지 않았지만 Animal 클래스를 상속했기 때문에 그대로 사용 가능함 bb

 

 

흠 그럼 이제 자식 클래스의 기능을 확장해보자

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

class Duck extends Animal {
    void sleep() {
        System.out.println(this.name+" zzz");
    }
}

public class Sample {
    public static void main(String[] args) {
        Duck duck = new Duck();
        dog.setName("duckling");
        System.out.println(duck.name);  // duckling
        duck.sleep();  // duckling zzz
    }
}

 

Duck 클래스에 잠을 자는 sleep 메서드를 추가해 보았다

Duck 클래스는 Animal 클래스보다 많은 기능을 갖게 되었다! 부모 클래스를 상속받은 자식 클래스는 부모 클래스의 기능+a의 기능을 가질 수 있게 되는 것이다 bb

 

 

IS-A 관계란 무엇일까...

Duck 클래스가 Animal 클래스의 하위 개념이기 때문에 Duck is a Animal과 같이 말할 수 있다

그렇다 바로 상속 관계인 것이다

이 관계에 있을 때 자식 클래스의 객체는 부모 클래스의 자료형인 것처럼 사용할 수 있다

Animal duck = new Duck();

 

이렇게 말이다!

하지만 Duck is a Animal 일 때 말이 되는 거지 Animal is a Duck은 말이 안되기 때문에

Duck duck = new Animal();

 

얘는 안된다 이러면 컴파일 오류가 난다ㅜ

 

 

오리에게 집을 지어줘보겠다

Duck 클래스를 상속하여 HouseDuck 클래스를 만들것이다 (Duck 클래스 구체화)

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

class Duck extends Animal {
    void sleep() {
        System.out.println(this.name+" zzz");
    }
}

class HouseDuck extends Duck {
}

public class Sample {
    public static void main(String[] args) {
        HouseDuck houseDuck = new HouseDuck();
        houseDuck.setName("ddori");
        houseDuck.sleep();  // ddori zzz 출력
    }
}

 

집 안에서 잠을 잘 수 있도록 해주자

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

class Duck extends Animal {
    void sleep() {
        System.out.println(this.name + " zzz");
    }
}

class HouseDuck extends Duck {
	void sleep() {
    	System.out.println(this.name + " zzz in house");
    }
}

public class Sample {
    public static void main(String[] args) {
        HouseDuck houseDuck = new HouseDuck();
        houseDuck.setName("ddori");
        houseDuck.sleep();  // ddori zzz in house 출력
    }
}

 

이렇게 부모 클래스의 메서드와 자식 클래스의 메서드가 동일한 형태이면 자식 클래스의 메서드를 호출한다

이것을 메서드 오버라이딩이라고 한다.

 

 

입력 항목이 다른 경우 동일한 이름의 메서드를 만들 수 있는데 이를 메서드 오버로딩이라고 한다 (추가)

주의 : 메서드의 입력 항목이 다를 것임

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

class Duck extends Animal {
    void sleep() {
        System.out.println(this.name + " zzz");
    }
}

class HouseDuck extends Duck {
    void sleep() {
        System.out.println(this.name + " zzz in house");
    }

    void sleep(int hour) {
        System.out.println(this.name + " zzz in house for " + hour + " hours");
    }  // 새로운 sleep 등장
}

public class Sample {
    public static void main(String[] args) {
        HouseDuck houseDuck = new HouseDuck();
        houseDuck.setName("ddori");
        houseDuck.sleep();  // ddori zzz in house 출력
        houseDuck.sleep(13);  // ddori zzz in house for 13 hours 출력
    }
}

 


생성자

생성자는 객체 변수에 값을 무조건 설정해야만 객체가 생성될 수 있도록 강제할 수 있다

생성자 규칙 : 클래스명 = 메서드명, 리턴 타입 정의 X

new 키워드로 호출함!

class HouseDog extends Dog {
    HouseDuck(String name) {  // 문자열 필요로하는 생성자
        this.setName(name);
    }
}

HouseDuck duck = new HouseDuck("ddori");  // 입력받은 문자열 출력

 

 

디폴트 생성자는 입력 항목과 내부 내용이 없는 생성자이다

class Duck extends Animal {
    Duck() {
    }

 

new Duck()로 Duck 클래스의 객체가 만들어질 때 디폴트 생성자 Duck()가 실행된다

클래스에 생성자가 하나도 없으면 자동으로 디폴트 생성자가 추가됨!

 

입력 항목이 다른 생성자를 여러 개 만드는 것을 생성자 오버로딩이라고 한다

(... 생략 ...)
class HouseDuck extends Duck {
    HouseDuck(String name) {
        this.setName(name); // String 입력 생성자 1
    }

    HouseDuck(int type) {
        if (type == 1) {
            this.setName("mallard");
        } else if (type == 2) {
            this.setName("pekinduck"); // int 입력 생성자 2
        }
    }
    
(... 생략 ...)
public class Sample {
    public static void main(String[] args) {
        HouseDuck ddori = new HouseDuck("ddori"); // 생성자 1
        HouseDuck mallard = new HouseDuck(1); // 생성자 2
        System.out.println(ddori.name);  // ddori 출력
        System.out.println(mallard.name);  // mallard 출력
    }
}

 


인터페이스

클래스가 추가될 때마다 메서드를 계속계속 추가해야 한다면 너무 복잡해진다... 이럴 때 인터페이스를 사용한다

인터페이스는 interface 키워드로 작성하고 inplements 키워드로 상속한다

interface Predator {
}

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

(... 생략 ...)

class Tiger extends Animal implements Predator {
}

class Lion extends Animal implements Predator {    
}

(... 생략 ...)

class ZooKeeper {
    void feed(Predator predator) {
        System.out.println("feed apple");
    }
}

// 변경전
// class ZooKeeper {
//     void feed(Tiger tiger) {
//         System.out.println("feed apple");
//     }
//
//     void feed(Lion lion) {
//         System.out.println("feed banana");
//     }
// }

(... 생략 ...)

 

feed 메서드 입력할 때 Tiger, Lion가 각각 필요했었지만 이제 Predator 인터페이스로 한 번에 처리가 가능해졌다

(이때 tiger은 Tiger 클래스의 객체이자 Predator 인터페이스의 객체)

이제 어떤 육식동물 클래스가 추가되더라도 두렵지 않다!

 

하지만 코드에 문제가 하나 있다... 항상 feed apple 만을 출력하게 된다는 것이다동물들이 각각 다른 먹이를 먹을 수 있게 해주자

interface Predator {
    String getFood();
}

(... 생략 ...)

class Tiger extends Animal implements Predator {
    public String getFood() {  // 인터페이스 메소드는 항상 public으로 구현하삼
        return "apple";
    }
}

class Lion extends Animal implements Predator {
    public String getFood() {
        return "banana";
    }
}

(... 생략 ...)

class ZooKeeper {
    void feed(Predator predator) {
        System.out.println("feed "+predator.getFood());
    }
}

(... 생략 ...)

// feed apple
// feed banana 출력

 

인터페이스 메서드는 메서드의 이름과 입출력에 대한 정의만 있고 내용은 없다Predator 인터페이스에 getFood 메서드를 추가하면 컴파일 오류가 발생하니 Tigor, Lion 클래스에 getFood 메서드를 추가하도록 하자Zookeeper 클래스에서도 predator.getFood()로 Predator 인터페이스를 구현한 구현체의 getFood 메서드가 호출된다동물들이 다 다른 먹이를 먹을 수 있게 되었다~~

 


다형성

동물 울음소리를 들어보자

class Bouncer {
    void barkAnimal(Animal animal) {
        if (animal instanceof Tiger) {
            System.out.println("어흥");
        } else if (animal instanceof Lion) {
            System.out.println("으르렁");
        }
    }
}

 

동물이 더 생겼다

class Bouncer {
    void barkAnimal(Animal animal) {
      if (animal instanceof Tiger) {
            System.out.println("어흥");
        } else if (animal instanceof Lion) {
            System.out.println("으르렁");
        } else if (animal instanceof Crocodile) {
          System.out.println("쩝쩝");
        } else if (animal instanceof Leopard) {
          System.out.println("캬옹");
        }
    }
}

 

역시 추가될수록 너무 복잡해진다... 그렇지만 우리는 인터페이스를 쓸 줄 알게 되었음

interface Barkable {
    void bark();
    
class Tiger extends Animal implements Predator, Barkable {
    public void bark() {
        System.out.println("어흥");
    }
}

class Lion extends Animal implements Predator, Barkable {
    public void bark() {
        System.out.println("으르렁");
    }
}

class Bouncer {
    void barkAnimal(Barkable animal) {  // Animal 대신 Barkable을 사용
        animal.bark();
    }
}

 

간편해졌다!

여기서에서 tiger은 Tiger 클래스의 객체이면서 Animal 클래스의 객체이고, Barkable 인터페이스와 Predator 인터페이스의 객체이다 (그래서 barkAnimal 메서드의 입력자료형이 Animal -> Barkable 이 될 수가 잇음)이렇게 하나의 객체가 여러 개의 자료형 타입을 가질 수 있는 것이 바로 다향성이다

 


추상 클래스

추상 클래스는 인터페이스의 역할과 클래스의 기능을 모두 하는 돌연변이이다

이거 만드려면 class 앞에 abstract를 표기해야한다구현체가 없어서 abstract 클래스를 상속하는 클래스에서 메서드를 구현해야 하기도 한다

 


QUIZ

1. 자바에서 한 클래스가 다른 클래스를 상속받을 때 사용하는 키워드는 무엇인가요?

- extends

 

2. 상속을 통해 자식 클래스가 부모 클래스에서 얻을 수 있는 이점은 무엇인가요?

- 코드 재사용성

 

3. 다중 상속이 허용되지 않는 이유 중 하나는 무엇인가요?

- 다이아몬드 문제로 인한 모호성

 

4. 상속된 메서드를 자식 클래스에서 재정의하려면 어떤 키워드를 사용하나요?

- 없음, 자동으로 재정의된다.

 

5. 자바에서 모든 클래스의 상위 클래스는 무엇인가요?

- Object

 

6. 생성자의 기본 역할은 무엇인가요?

- 클래스의 객체를 초기화한다

 

7. 생성자는 언제 호출되나요?

- 객체가 생성될 때

 

8. 생성자를 오버로딩할 때 중요한 조건은 무엇인가요?

- 서로 다른 매개변수를 가져야 한다

 

9. 인터페이스의 특징으로 옳은 것은 무엇인가요?

- 메서드의 구현을 포함할 수 없다

 

10. 인터페이스의 메서드 구현을 클래스에서 강제하기 위한 키워드는 무엇인가요?

- inplements

 

11. 다형성의 주요 특징은 무엇인가요?

- 동일한 인터페이스로 다양한 구현을 사용할 수 있다

 

12. 오버라이딩과 오버로딩의 차이점으로 올바른 것은 무엇인가요?

- 오버라이딩은 상속 관계에서 이루어진다

 

13. 추상 클래스의 주요 특징으로 옳은 것은 무엇인가요?

- 인스턴스를 만들 수 없다

 

14. 추상 클래스와 인터페이스의 공통점은 무엇인가요?

- 둘 다 객체를 생성할 수 없다

 

15. 추상 메서드는 어떤 키워드를 사용하여 선언하나요?

- abstract

 

서술형1. 추상 클래스와 일반 클래스의 차이점을 설명하세요.

- 추상 클래스는 객체를 생성할 수 없고 일반 클래스는 객체 생성을 통해 인스턴스를 만들 수 있다.

 

서술형2. 추상 클래스와 인터페이스를 함께 사용하는 이유와 예를 설명하세요.- 추상 클래스로 공통 기능을 제공하고 인터페이스로 특정 메서드를 강제하여 코드를 보다 간단하게 만들 수 있다.

 

서술형3. 추상 클래스를 상속받은 자식 클래스에서 추상 메서드를 구현해야 하는 이유를 설명하세요.- 추상 클래스의 추상 메서드는 구현체가 없어 구현되지 않았기 때문에 자식 클래스에서 해당 메서드의 구체적인 동작을 정해줘야한다.

 

서술형4. 추상 클래스에서 선언된 일반 메서드와 추상 메서드의 차이점을 설명하세요.- 일반 메서드는 구현이 포함되어 있고 추상 메서드는 구현이 없어 자식 클래스에서 별도의 구현이 필요하다.

 

서술형5. 상속과 구성의 차이점과 각각의 장단점을 비교하세요.- 상속은 부모 클래스의 속성을 자식 클래스에서도 사용할 수 있게 해 코드의 재사용성을 높이지만, 코드 의존성이 높아진다는 단점도 있다. 구성은 부모 클래스의 속성과 자식 클래스의 속성이 독립적이어서 유연성이 높지만, 코드가 복잡해진다는 단점이 있다. 

 

서술형6. 생성자에서 this와 super 키워드의 차이점에 대해 설명하세요.- this는 현재 인스턴스를 참조하고 클래스 내의 생성자를 호출할 때 사용한다. 반면 super은 부모 클래스의 생성자를 호출할 때 사용한다.

 

서술형7. 자바에서 인터페이스와 추상 클래스의 차이점을 설명하세요.- 인터페이스는 다중 상속을, 추상 클래스는 단일 상속을 지원한다.

 

서술형8. 다형성의 개념과 객체지향 프로그래밍에서의 중요성을 설명하세요.- 다형성은 하나의 객체가 다양한 자료형을 가져 코드의 재사용성을 높인다.

 

서술형9. 메서드 오버로딩과 오버라이딩의 차이점을 비교하세요.- 메서드 오버로딩은 같은 클래스 내 같은 이름의 메서드를 여러 개 정의하지만 메서드의 입력 항목이 달라야한다. 반면 메서드 오버라이딩은 부모 클래스의 메서드를 자식 클래스에서 재정의 하는 것으로 메서드의 입력 항목이 같아야한다.

 

서술형10. 동적 바인딩의 개념을 설명하고 다형성과의 관계를 설명하세요.- 동작 바인딩은 프로그램 실행 시 메서드 호출이 실제 객체의 타입에 따라 결정되는 것으로 다형성은 동적 바인딩을 기반으로 두어 유연성과 확장성을 높인다.

 


코딩 테스트

1.

class Animal {
    String sound = "generic sound";
}

class Dog extends Animal {
    String sound = "bark";
}

public class Main {
    public static void main(String[] args) {
        Animal a = new Dog();
        System.out.println(a.sound);
    }
}

- generic sound 

 

2.

class Parent {
    void show() {
        System.out.println("Parent");
    }
}

class Child extends Parent {
    void show() {
        System.out.println("Child");
    }
}

public class Main {
    public static void main(String[] args) {
        Parent p = new Child();
        p.show();
    }
}

- Child

 

3.

class Parent {
    int x = 10;
}

class Child extends Parent {
    int x = 20;
}

public class Main {
    public static void main(String[] args) {
        Parent p = new Child();
        System.out.println(p.x);
    }
}

- 10

 

4.

class A {
    A() {
        System.out.println("A's constructor");
    }
}

class B extends A {
    B() {
        System.out.println("B's constructor");
    }
}

public class Main {
    public static void main(String[] args) {
        B obj = new B();
    }
}

- A's constructor

  B's constructor

 

5.

class A {
    A() {
        System.out.println("A's no-arg constructor");
    }
}

class B extends A {
    B() {
        System.out.println("B's no-arg constructor");
    }
}

public class Main {
    public static void main(String[] args) {
        A a = new B();
    }
}

- A's no-arg constructor

  B's no-arg constructor 

 

6.

interface A {
    void display();
}

interface B extends A {
    void display();
}

class C implements B {
    public void display() {
        System.out.println("C's display");
    }
}

public class Main {
    public static void main(String[] args) {
        A obj = new C();
        obj.display();
    }
}

- C's display

 

7.

interface A {
    default void show() {
        System.out.println("A");
    }
}

class B implements A {
    public void show() {
        System.out.println("B");
    }
}

public class Main {
    public static void main(String[] args) {
        A obj = new B();
        obj.show();
    }
}

- B

반응형