본문 바로가기

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

[2024-2 Java 스터디] 이가인 #4주차

반응형

목차

  • 상속
    • 자식 클래스의 기능 확장
    • IS - A 관계 (상속 관계)
    • 메서드 오버라이딩 (메서드 덮어쓰기)
    • 메서드 오버로딩
    • 다중상속
  • 생성자
    • 정의, 생성자 규칙
    • 디폴트 생성자
    • 생성자 오버로딩
  • 인터페이스
    • 필요한 이유
    • 디폴트 메서드
  • 다형성
  • 추상클래스
    • 정의, 특징

 


상속

자식 클래스의 기능확장
  • 보통 자식 클래스는 부모 클래스의 기능에 더하여 좀 더 많은 기능을 갖도록 작성할 수 있다.
class Animal {
    String name;

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

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

public class Sample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setName("poppy");
        System.out.println(dog.name);
        dog.sleep();
    }
}

extends를 사용하여 이런식으로 사용한다. 

IS - A 관계
Animal dog = new Dog();  // Dog is a Animal
  • 상속 관계
  • Dog(자식)은 Animal(부모)의 하위개념
  • Dog is Animal ( 개는 동물이다 )  --> O
  • Animal is Dog ( 동물은 개다)  --> (개념적으로 이상함)
  • Dog(자식클래스로 ) 만들어진 객체를 Animal의 자료형으로 쓸 수 있음
    -- > 반대는 컴파일 오류 발생  ( Dog dog = new Animal(); )
메서드 오버라이딩 ( 덮어쓰기 )
  • 부모 클래스의 메서드(sleep)를 자식 클래스가 동일한 형태로 또다시 구현하는 행위
    • 우선순위를 가짐
class Animal {
    String name;

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

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

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

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

 

 

메서드 오버로딩
  • 입력 항목(매개변수)이 다른 경우 동일한 이름의 메서드를 만들 수 있는데 이를 메서드 오버로딩(method overloading)이라고 부른다. -> sleep(), sleep(int hour)
class Animal {
    String name;

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

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

class HouseDog extends Dog {
    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");
    }
}

public class Sample {
    public static void main(String[] args) {
        HouseDog houseDog = new HouseDog();
        houseDog.setName("happy");
        houseDog.sleep();  // happy zzz in house 출력
        houseDog.sleep(3);  // happy zzz in house for 3 hours 출력
    }
}

 

다중 상속
  • 클래스가 동시에 하나 이상의 클래스를 상속받는 것을 뜻함
    --> 다이아몬드 problems...
  • 자바는 다중 상속을 하지 못한다!!!

 

 

 

 


생성자

정의, 생성자 규칙
  • 메서드명이 클래스명과 동일하고 리턴 자료형을 정의하지 않는 메서드를 생성자라고 한다
(... 생략 ...)

class HouseDog extends Dog {
    HouseDog(String name) {
        this.setName(name);
    }

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

(... 생략 ...)

HouseDog가 생성자이다. 

 

 

 

생성자 규칙

1. 클래스명과 메서드명이 같다
2. 리턴 타입을 정의하지 않는다(void도 사용하지 않는다)
  • 생성자는 객체가 생성될 때 호출된다.
  • 즉, 생성자는 다음과 같이 new 키워드가 사용될 때 호출된다.

--> 호출시 문자열을 필요로 하는 생성자이면 생성자 생성(호출) 시 문자열을 전달해야한다. 

HouseDog(String name) {
    this.setName(name);
} 

HouseDog dog = new HouseDog("happy");   // 생성자 호출 시 문자열을 전달해야 한다.



디폴트 생성자
class Dog extends Animal {
    Dog() {
    }

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

 

Dog() {} 처럼 생성자의 입력 항목이 없고 생성자 내부에 아무 내용이 없는 이와 같은 생성자를 디폴트 생성자라고 부른다

- 디폴트 생성자를 구현하면 new Dog()로 Dog 클래스의 객체가 만들어질때 디폴트 생성자 Dog()가 실행될 것이다.

- 만약 클래스에 생성자가 하나도 없다면 컴파일러는 자동으로 이와 같은 디폴트 생성자를 추가한다. 하지만 사용자가 작성한 생성자가 하나라도 구현되어 있다면 컴파일러는 디폴트 생성자를 추가하지 않는다.

 

생성자 오버로딩

 

class HouseDog extends Dog {
    HouseDog(String name) {
        this.setName(name);
    }

    HouseDog(int type) {
        if (type == 1) {
            this.setName("yorkshire");
        } else if (type == 2) {
            this.setName("bulldog");
        }
    }
    
public class Sample {
    public static void main(String[] args) {
        HouseDog happy = new HouseDog("happy");
        HouseDog yorkshire = new HouseDog(1);
        System.out.println(happy.name);  // happy 출력
        System.out.println(yorkshire.name);  // yorkshire 출력
    }



HouseDog에서 int 자료형과 String자료형의 입력을 받는 생성자 두 개가 있다. 

--> 이처럼 입력 항목이 다른 생성자를 여러 개 만들 수 있는데 이런 것을 생성자 오버로딩이라고 한다. 

---> 메서드 오버로딩과 동일한 개념이다. 

 

 


인터페이스

필요한 이유

 

클래스가 추가될 때 마다 메서드를 추가해야될 때 클래스가 복잡해짐 -> 이런 어려움을 극복하기 위해 인터페이스의 도움이 필요하다.

 

--> 보통 중요 클래스(ZooKeeper)를 작성하는 시점에서는 클래스(Animal)의 구현체(Tiger, Lion)가 몇 개가 될지 알 수 없으므로 인터페이스(Predator)를 정의하여 인터페이스를 기준으로 메서드(feed)를 만드는 것이 효율적이다.

인터페이스 메서드

인터페이스의 메서드는 메서드의 이름과 입출력에 대한 정의만 있고 그 내용은 없다. 그 이유는 인터페이스는 ‘규칙’이기 때문이다. 즉, getFood 메서드는 인터페이스를 implements한 클래스들이 강제적으로 구현해야 하는 규칙이 된다.

interface Predator {
    String getFood();
}

(... 생략 ...)

이제 Predator 인터페이스에 getFood메서드를 추가하면 Tiger, Lion 등의 Predator 인터페이스를 구현한 클래스에서 컴파일 오류가 발생할 것이다. 오류를 해결하려면 다음처럼 Tiger, Lion 클래스에 getFood 메서드를 구현해야 한다.

(... 생략 ...)

class Tiger extends Animal implements Predator {
    public String getFood() {
        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());
    }
}

(... 생략 ...)

 

또한 인터페이스의 메서드는 항상 public으로 구현해야한다!

 

 

디폴트 메서드
interface Predator {
    String getFood();

    default void printFood() {
        System.out.printf("my food is %s\n", getFood());
    }
}

--> 이렇게 앞에 default라고 표기하고 디폴트메서드를 구현한다면 

- Predator 인터페이스를 구현한 Tiger, Lion 등의 실제 클래스는 printFood 메서드를 구현하지 않아도 사용할 수 있다.

- 디폴트 메서드에서 오버라이딩이 가능하다

 



 

 


다형성

 

변경 전

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

 

 

변경 후 (복잡했던 코드가 명확해짐)

void barkAnimal(Barkable animal) {
    animal.bark();
}

 

tiger, lion 객체는 각각 Tiger, Lion 클래스의 객체이면서 Animal 클래스의 객체이기도 하고, Barkable과 Predator 인터페이스의 객체이기도 하다

--> barkAnimal메서드의 입력 자료형을 Animal에서 Barkable로 바꾸어 사용할 수 있는 것!

 

➡️ 하나의 객체가 여러 개의 자료형 타입을 가질 수 있는 것을 객체 지향 세계에서는 다형성이라고 한다

- 일반 클래스는 단일 상속만 가능하다.

- 다형성을 이용하면 복잡한 형태의 분기문을 간단하게 처리할 수 있는 경우가 많다

 

또한 Tiger 클래스의 객체는 다음과 같이 여러가지 자료형으로 표현할 수 있다

Tiger tiger = new Tiger();  // Tiger is a Tiger
Animal animal = new Tiger();  // Tiger is a Animal
Predator predator = new Tiger();  // Tiger is a Predator
Barkable barkable = new Tiger();  // Tiger is a Barkable

중요한 점은 Predator로 선언된 predator 객체와 Barkable로 선언된 barkable 객체는 사용할 수 있는 메서드가 서로 다르다는 점이다.  

predator 객체는 getFood() 메서드가 선언된 Predator 인터페이스의 객체이므로 getFood 메서드만 호출이 가능하다. 이와 마찬가지로 Barkable로 선언된 barkable 객체는 bark 메서드만 호출이 가능하다.




추상클래스

정의, 특징
  • 인터페이스의 역할도 하면서 클래스의 기능도 가지고 있는 자바의 ‘돌연변이’ 같은 클래스이다
  • 추상 클래스를 만들려면 class앞에 abstract를 표기해야한다. 
  • abstract 메서드는 인터페이스의 메서드와 마찬가지로 구현체가 없다.
    • 즉, abstract 클래스를 상속하는 클래스에서 해당 abstract 메서드를 구현해야만 함
    • Animal 클래스의 기능을 유지하기 위해 Animal 클래스를 상속했다
  • 추상 클래스는 일반 클래스와 달리 단독으로 객체를 생성할 수 없다. 반드시 추상 클래스를 상속한 실제 클래스를 통해서만 객체를 생성할 수 있다.
abstract class Predator extends Animal {
    (... 생략 ...)
}

interface Barkable {
    (... 생략 ...)
}

interface BarkablePredator extends Predator, Barkable {
}

class Animal {
    (... 생략 ...)
}

class Tiger extends Predator implements Barkable {
    (... 생략 ...)
}

class Lion extends Predator implements Barkable {
    (... 생략 ...)
}

class ZooKeeper {
    (... 생략 ...)
}

class Bouncer {
    (... 생략 ...)
}

public class Sample {
    (... 생략 ...)
}

 

인터페이스와 추상 클래스의 차이

 

추상 클래스는 인터페이스와 달리 일반 클래스처럼 객체 변수, 생성자, private 메서드 등을 가질 수 있다.



 


퀴즈

객관식문제

  1. 자바에서 한 클래스가 다른 클래스를 상속받을 때 사용하는 키워드는 무엇인가요?
    • a) super
    • b) extends
    • c) inherits
    • d) this
  2. 상속을 통해 자식 클래스가 부모 클래스에서 얻을 수 있는 이점은 무엇인가요?
    • a) 코드 재사용성
    • b) 객체 간의 독립성 강화
    • c) 코드 복잡도 증가
    • d) 상속된 필드의 자동 초기화
  3. 다중 상속이 허용되지 않는 이유 중 하나는 무엇인가요?
    • a) 코드의 재사용성
    • b) 메모리 절약
    • c) 다이아몬드 문제로 인한 모호성
    • d) 컴파일 시간 단축
  4. 상속된 메서드를 자식 클래스에서 재정의하려면 어떤 키워드를 사용하나요?
    • a) super
    • b) abstract
    • c) override
    • d) 없음, 자동으로 재정의된다.
  5. 자바에서 모든 클래스의 상위 클래스는 무엇인가요?
    • a) Object
    • b) Base
    • c) Class
    • d) Parent
  6. 생성자의 기본 역할은 무엇인가요?
    • a) 클래스의 메서드를 초기화한다
    • b) 클래스의 객체를 초기화한다
    • c) 클래스의 인터페이스를 정의한다
    • d) 클래스의 속성을 변경한다
  7. 생성자는 언제 호출되나요?
    • a) 클래스가 선언될 때
    • b) 객체가 생성될 때
    • c) 프로그램이 종료될 때
    • d) 메서드가 호출될 때
  8. 생성자를 오버로딩할 때 중요한 조건은 무엇인가요?
    • a) 같은 매개변수를 사용해야 한다
    • b) 서로 다른 매개변수를 가져야 한다
    • c) 이름이 달라야 한다
    • d) 반환 타입이 있어야 한다
  9. 인터페이스의 특징으로 옳은 것은 무엇인가요?
    • a) 필드와 메서드를 포함한다
    • b) 메서드의 구현을 포함할 수 없다
    • c) 객체를 생성할 수 있다
    • d) final 키워드로 선언해야 한다
  10. 인터페이스의 메서드 구현을 클래스에서 강제하기 위한 키워드는 무엇인가요?
    • a) implements
    • b) extends
    • c) interface
    • d) super
  11. 다형성의 주요 특징은 무엇인가요?
    • a) 여러 형태로 객체를 생성할 수 있다
    • b) 여러 클래스를 상속할 수 있다
    • c) 동일한 인터페이스로 다양한 구현을 사용할 수 있다
    • d) 모든 필드가 자동으로 초기화된다
  12. 오버라이딩과 오버로딩의 차이점으로 올바른 것은 무엇인가요?
    • a) 오버라이딩은 메서드 이름이 다르다
    • b) 오버로딩은 반환 타입이 다르다
    • c) 오버라이딩은 상속 관계에서 이루어진다
    • d) 오버로딩은 상속 관계에서 이루어진다
  13. 추상 클래스의 주요 특징으로 옳은 것은 무엇인가요?
    • a) 객체 생성이 가능하다
    • b) 모든 메서드를 추상 메서드로 선언해야 한다
    • c) 인스턴스를 만들 수 없다
    • d) static 메서드를 가질 수 없다
  14. 추상 클래스와 인터페이스의 공통점은 무엇인가요?
    • a) 둘 다 객체를 생성할 수 없다
    • b) 둘 다 모든 메서드를 구현해야 한다
    • c) 둘 다 필드를 가질 수 없다
    • d) 둘 다 final로 선언해야 한다
  15. 추상 메서드는 어떤 키워드를 사용하여 선언하나요?
    • a) static
    • b) void
    • c) abstract
    • d) final

서술형 문제

  1. 추상 클래스와 일반 클래스의 차이점을 설명하세요.
    • 추상 클래스는 인스턴스를 생성할 수 없고, 추상 메서드를 가질 수 있다. 반면, 일반 클래스는 모든 메서드가 구현되어 있고 인스턴스 생성이 가능하다.
  2. 추상 클래스와 인터페이스를 함께 사용하는 이유와 예를 설명하세요.
    • 추상 클래스는 공통 동작을 제공하고, 인터페이스는 다중 구현을 지원해 유연한 설계를 가능하게 한다.
  3. 추상 클래스를 상속받은 자식 클래스에서 추상 메서드를 구현해야 하는 이유를 설명하세요.
    • 추상 메서드는 자식 클래스에서 구체적으로 정의되어야 하며, 이를 통해 다형성을 실현할 수 있다.
  4. 추상 클래스에서 선언된 일반 메서드와 추상 메서드의 차이점을 설명하세요.
    • 일반 메서드는 구현이 포함된 메서드고, 추상 메서드는 선언만 되어 있어 자식 클래스에서 반드시 구현해야 한다.
  5. 상속과 구성(Composition)의 차이점과 각각의 장단점을 비교하세요.
    • 상속은 "is-a" 관계로 부모의 기능을 확장하며, 구성은 "has-a" 관계로 클래스가 다른 객체를 포함하는 방식이다. 상속은 코드 재사용이 쉽지만 덜 유연하고, 구성은 유연성이 높다.
  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

반응형