개발자교육

24_07_08 오늘의 수업내용

regnator 2024. 7. 8. 17:12
728x90
반응형

사전정리

User 생성자 메서드

  • 선언 이름: User
  • 호출 이름: User
  • 입력받는 갯수가 없는 생성자 메서드 호출: User();

생성자 메서드 선언 코드:

public User() {
}
 

객체 생성 및 할당:

User u01 = new User();

입력받는 갯수가 없는 일반 메서드

  • 일반 메서드 선언 코드:
public void User() {
}
public static void User() {
}

 

요약

  • 생성자 메서드 이름은 클래스명과 같다.
  • 입력받는 갯수가 없는 생성자 메서드 호출: User();
  • 생성자 메서드 선언 코드: public User() { }
  • 입력받는 갯수가 없는 일반 메서드 선언 코드: public void User() { } (또는 public static void User() { })

생성자

생성자(Constructor)는 객체지향 프로그래밍에서 클래스로부터 객체(인스턴스)를 생성할 때 호출되는 특별한 메서드. 주로 객체의 초기화 작업을 수행하며, 클래스 이름과 동일하게 선언.

 

생성자의 주요 특징

  1. 이름과 클래스 이름이 동일:
    • 생성자의 이름은 클래스의 이름과 동일해야 한다. 예를 들어, 클래스가 Car라면 생성자도 Car로 정의.
  2. 인스턴스화 시 자동 호출:
    • 객체를 생성할 때 new 키워드와 함께 생성자가 자동으로 호출된다. 이 과정에서 객체의 초기화 작업이 수행된다.
  3. 리턴 타입이 없음:
    • 생성자는 리턴 타입을 가지지 않는다. 즉, void나 다른 데이터 타입을 지정하지 않는다.
  4. 오버로딩 가능:
    • 여러 개의 생성자를 정의하여 다양한 인자를 받거나 다양한 초기화 방법을 제공할 수 있다. 이를 생성자 오버로딩이라고 한다.
  5. 자동 호출:
    • 클래스의 인스턴스를 생성할 때 생성자는 자동으로 호출. 명시적으로 호출하는 것이 아니라 객체가 생성될 때 내부적으로 실행.

생성자의 예시

class Car {
    private String model;
    private int year;

    // 기본 생성자
    public Car() {
        this.model = "Unknown";
        this.year = 2023;
    }

    // 매개변수가 있는 생성자 (생성자 오버로딩)
    public Car(String model, int year) {
        this.model = model;
        this.year = year;
    }

    // 메서드: 모델과 연도 반환
    public String getModel() {
        return model;
    }

    public int getYear() {
        return year;
    }
}

public class Main {
    public static void main(String[] args) {
        // 기본 생성자로 객체 생성
        Car car1 = new Car();
        System.out.println("Car 1: Model=" + car1.getModel() + ", Year=" + car1.getYear());

        // 매개변수가 있는 생성자로 객체 생성
        Car car2 = new Car("Tesla", 2022);
        System.out.println("Car 2: Model=" + car2.getModel() + ", Year=" + car2.getYear());
    }
}

생성자 사용 요약

  • 생성자는 클래스의 인스턴스화 과정에서 객체 초기화를 담당.
  • 생성자는 객체가 생성될 때 자동으로 호출되며, 클래스 이름과 동일.
  • 생성자는 리턴 타입이 없고, 오버로딩을 통해 여러 개의 생성자를 정의할 수 있다.
  • 객체를 생성할 때 매개변수를 전달하여 원하는 초기화 상태로 객체를 생성할 수 있다.

생성자 오버로딩

설명

생성자 오버로딩은 하나의 클래스에서 여러 개의 생성자를 정의하는 것을 의미.

각 생성자는 서로 다른 매개변수 리스트를 가지며, 이를 통해 객체를 다양한 방법으로 초기화할 수 있다.

생성자 오버로딩을 사용하면 객체 생성 시 필요한 매개변수에 따라 적절한 생성자가 호출된다.

예시)
public class User {
    
    // 기본 생성자: 매개변수가 없음
    public User() {
        System.out.println("기본 생성자 호출");
    }
    
    // 이름을 매개변수로 받는 생성자
    public User(String name) {
        System.out.println("이름을 입력받는 생성자 호출: " + name);
    }
    
    // 이름과 나이를 매개변수로 받는 생성자
    public User(String name, int age) {
        System.out.println("이름과 나이를 입력받는 생성자 호출: " + name + ", " + age);
    }
}
----------------------------------------------------------------------------
사용예시)
// 기본 생성자를 호출
User user1 = new User(); // 출력: 기본 생성자 호출

// 이름을 입력받는 생성자를 호출
User user2 = new User("Alice"); // 출력: 이름을 입력받는 생성자 호출: Alice

// 이름과 나이를 입력받는 생성자를 호출
User user3 = new User("Bob", 25); // 출력: 이름과 나이를 입력받는 생성자 호출: Bob, 25
  • 이 예시에서는 User 클래스가 다양한 생성자를 가지고 있어 다양한 방법으로 객체를 초기화할 수 있다.
  • 이를 통해 코드의 유연성과 가독성을 높일 수 있다.

 

사용이유

  1. 다양한 초기화 방식 제공: 다양한 매개변수 조합으로 객체를 초기화할 수 있어 유연성을 높인다.
  2. 코드 가독성 향상: 여러 생성자를 통해 객체를 직관적으로 초기화할 수 있어 코드의 가독성이 높아진다.
    • 즉, 생성자가 명확하게 어떤 데이터를 받아서 객체를 초기화하는지 나타낸다는 뜻이다. 이는 개발자가 객체를 생성할 때 각 생성자가 어떤 역할을 하는지 쉽게 이해할 수 있도록 돕는다.  
  3. 디폴트 값 제공: 기본 생성자나 일부 매개변수를 가지는 생성자를 제공함으로써 디폴트 값을 쉽게 설정할 수 있다.
예시)
public class User {
    private String name;
    private int age;
    private String email;

    // 기본 생성자
    public User() {
        this.name = "Unknown";
        this.age = 0;
        this.email = "Unknown";
    }

    // 이름을 입력받는 생성자
    public User(String name) {
        this.name = name;
        this.age = 0;
        this.email = "Unknown";
    }

    // 이름과 나이를 입력받는 생성자
    public User(String name, int age) {
        this.name = name;
        this.age = age;
        this.email = "Unknown";
    }

    // 이름, 나이, 이메일을 입력받는 생성자
    public User(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }

    // getter와 setter 메서드는 생략
}
-----------------------------------------------------------------------------
사용예시)
User user1 = new User(); // 기본 생성자 호출
User user2 = new User("Alice"); // 이름을 입력받는 생성자 호출
User user3 = new User("Bob", 25); // 이름과 나이를 입력받는 생성자 호출
User user4 = new User("Charlie", 30, "charlie@example.com"); // 이름, 나이, 이메일을 입력받는 생성자 호출

Static (정적) 키워드

개념

static 키워드는 클래스 멤버(변수, 메서드, 블록)를 정의할 때 사용되는 키워드.

static으로 정의된 멤버는 클래스의 인스턴스가 아닌 클래스 자체에 속하게 된다.

따라서 인스턴스가 생성될때가 아닌 클래스가 로드될 때 생성되고, 모든 인스턴스가 이 멤버를 공유한다.

 

주요 특징

  1. 클래스 레벨 소속: static 멤버는 클래스 자체에 속하며, 인스턴스(객체)에 속하지 않는다.
    • 따라서, static 멤버는 클래스에 속하기 때문에 인스턴스를 생성하지 않고 클래스 이름으로 직접 접근할 수 있다.
  2. 공유: 모든 인스턴스가 static 멤버를 공유한다. 하나의 인스턴스에서 변경된 static 멤버는 모든 인스턴스에 영향을 미친다.
  3. 메모리 절약: static 멤버는 메모리에 한 번만 로드되기 때문에 메모리 사용량을 줄일 수 있다.

사용 예시

1. static 변수:

public class Counter {
    public static int count = 0; // static 변수 선언

    public Counter() {
        count++; // 인스턴스가 생성될 때마다 count 증가
    }
}

public class Main {
    public static void main(String[] args) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        Counter c3 = new Counter();
        
        System.out.println(Counter.count); // 출력: 3
    }
}
  • count 변수는 static으로 선언되었기 때문에 모든 Counter 인스턴스가 이를 공유한다
  • 인스턴스를 생성할 때마다 count가 증가하며, 최종적으로 3이 출력된다.

2. static 메서드:

public class MathUtil {
    public static int add(int a, int b) {
        return a + b; // static 메서드 선언
    }
}

public class Main {
    public static void main(String[] args) {
        int result = MathUtil.add(5, 3); // static 메서드 호출
        System.out.println(result); // 출력: 8
    }
}
  • add 메서드는 static으로 선언되었기 때문에, 인스턴스를 생성하지 않고 MathUtil.add(5, 3) 형태로 호출할 수 있다.

3. static 블록:

public class StaticBlockExample {
    public static int value;
    
    // static 블록: 클래스가 로드될 때 실행됨
    static {
        value = 10;
        System.out.println("Static block executed.");
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(StaticBlockExample.value); // 출력: 10
    }
}
  • static 블록은 클래스가 로드될 때 한 번 실행된다.
  • 예시에서  StaticBlockExample 클래스가 로드될 때 value가 10으로 초기화되고, "Static block executed."가 출력된다.

메서드 상속

개념

메서드 상속은 객체지향 프로그래밍(OOP)에서 한 클래스가 다른 클래스의 메서드를 물려받는 것을 의미.

이는 코드 재사용성을 높이고 유지보수를 쉽게 하기 위해 사용.

자식 클래스는 부모 클래스의 메서드와 변수를 상속받아 사용할 수 있다.

 

사용이유

상속은 객체지향 프로그래밍에서 중요한 개념으로, 클래스 간의 계층 구조를 구축하여 코드의 재사용성을 높이고 다형성을 지원하는 기능을 제공

  1. 코드 재사용성:
    • 상속을 통해 기존의 클래스를 확장하여 새로운 클래스를 만들 수 있다. 이는 중복 코드를 줄이고, 유사한 기능을 가진 클래스들 간의 공통 로직을 부모 클래스에 정의할 수 있다.
  2. 계층 구조 구축:
    • 상속을 이용하면 클래스들 사이의 계층 구조(hierarchy)를 구축할 수 있다. 이는 논리적으로 객체들을 분류하고 관리할 수 있는 장점을 제공. 예를 들어, Animal 클래스를 부모 클래스로 하여 Dog, Cat, Bird 등의 클래스를 만들어 각각의 동물들을 표현할 수 있다.
  3. 다형성(polymorphism) 지원:
    • 상속을 통해 부모 클래스 타입으로 자식 클래스 객체를 참조할 수 있다. 이는 다형성을 지원하며, 동일한 메서드 호출이 각 객체의 구체적인 구현에 따라 다르게 동작할 수 있도록 한다.
    • 즉, 다형성은 프로그램에서 유연성을 제공하며, 동일한 코드가 여러 객체들에 대해 다르게 동작할 수 있도록 한다.

장점

  1. 유지보수 용이성:
    • 공통적인 기능이나 로직은 부모 클래스에 구현하여 한 곳에서 관리할 수 있다. 이로 인해 수정이 필요할 때 부모 클래스만 수정하면 되므로 유지보수가 용이해진다.
  2. 확장성:
    • 새로운 기능이 필요한 경우 기존의 클래스를 수정하지 않고, 새로운 클래스를 추가하여 확장할 수 있다. 이는 시스템의 유연성을 높이는 데 기여다.
  3. 코드 중복 최소화:
    • 공통적인 기능이나 속성을 상속을 통해 여러 클래스에서 재사용할 수 있기 때문에 코드의 중복을 최소화할 수 있다. 이는 코드의 가독성을 높이고 코드의 양을 줄이는 데 도움을 준다.
  4. 프로그래밍 시간 단축:
    • 상속을 통해 기본적인 기능을 제공하는 클래스를 만들고, 이를 확장하여 필요한 기능을 추가하는 방식으로 빠르게 개발할 수 있다. 따라서 개발 시간을 단축할 수 있는 장점이 있다.
  5. 객체 지향적 설계:
    • 상속을 사용하면 객체 지향적인 설계 원칙을 따르며, 코드의 구조화와 유연성을 높일 수 있다. 이는 대규모 프로젝트에서 코드의 구조를 보다 명확하게 만들어 유지보수와 확장을 용이하게 한다.

주요 특징

  1. 상속(Inheritance): 자식 클래스가 부모 클래스의 메서드를 물려받는다.
  2. 오버라이딩(Overriding): 자식 클래스는 필요에 따라 부모 클래스의 메서드를 재정의할 수 있다.
  3. 접근 제어: 상속받는 메서드는 접근 제어자에 따라 사용 가능 여부가 결정된다. public과 protected 메서드는 상속받아 사용 가능하지만, private 메서드는 상속되지 않는다.
  4. super 키워드: 자식 클래스에서 부모 클래스의 메서드를 호출할 때 사용.

예시)

1. 기본 상속

// 부모 클래스
class Animal {
    public void eat() {
        System.out.println("This animal eats food.");
    }
}

// 자식 클래스
class Dog extends Animal {
    public void bark() {
        System.out.println("The dog barks.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();  // 부모 클래스의 메서드를 호출
        dog.bark(); // 자식 클래스의 메서드를 호출
    }
}
  • 설명: Dog 클래스는 Animal 클래스를 상속받았기 때문에 Animal 클래스의 eat 메서드를 사용할 수 있다.

2. 메서드 오버라이딩

// 부모 클래스
class Animal {
    public void eat() {
        System.out.println("This animal eats food.");
    }
}

// 자식 클래스
class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("The dog eats dog food.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();  // 자식 클래스에서 오버라이딩한 메서드를 호출
    }
}
  • 설명: Dog 클래스는 Animal 클래스의 eat 메서드를 오버라이딩하여, "The dog eats dog food." 메시지를 출력하도록 재정의.

3. super 키워드 사용

// 부모 클래스
class Animal {
    public void eat() {
        System.out.println("This animal eats food.");
    }
}

// 자식 클래스
class Dog extends Animal {
    @Override
    public void eat() {
        super.eat(); // 부모 클래스의 메서드를 호출
        System.out.println("The dog eats dog food.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();  // 부모 클래스와 자식 클래스의 메서드가 모두 호출됨
    }
}
  • 설명: Dog 클래스의 eat 메서드는 super.eat()을 사용하여 부모 클래스의 eat 메서드를 호출한 후, 자식 클래스에서 추가로 정의한 내용을 출력.

인터페이스(Interface)

자바에서 인터페이스는 클래스가 구현해야 하는 메서드의 집합을 정의하는 일종의 틀. 인터페이스는 다음과 같은 몇 가지 중요한 특성을 가지고 있다:

 

1. 메서드 선언: 인터페이스는 메서드의 시그니처만 정의하고, 실제 구현은 제공하지 않는다. 즉, 메서드의 이름, 반환 타입, 매개변수만 정의하고, 메서드 본체는 작성하지 않는다.

public interface Animal {
    void eat();
    void sleep();
}

 

2. 구현(Implementation): 클래스가 인터페이스를 구현할 때는 인터페이스에 선언된 모든 메서드를 구현해야 한다. 한 클래스는 여러 인터페이스를 구현할 수 있다.

public class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }

    @Override
    public void sleep() {
        System.out.println("Dog is sleeping");
    }
}

 

3. 다중 상속: 자바는 클래스의 다중 상속을 지원하지 않지만, 인터페이스를 통해 다중 상속과 유사한 효과를 얻을 수 있다. 한 클래스가 여러 인터페이스를 구현할 수 있으므로, 다양한 행동을 정의하고 조합할 수 있다.

public interface Runnable {
    void run();
}

public class SuperDog implements Animal, Runnable {
    @Override
    public void eat() {
        System.out.println("SuperDog is eating");
    }

    @Override
    public void sleep() {
        System.out.println("SuperDog is sleeping");
    }

    @Override
    public void run() {
        System.out.println("SuperDog is running");
    }
}

 

4. 디폴트 메서드(Default Method): 자바 8부터 인터페이스에 디폴트 메서드를 정의할 수 있다. 이는 인터페이스가 일부 메서드의 기본 구현을 제공할 수 있게 한다.

public interface Animal {
    void eat();
    void sleep();

    default void breathe() {
        System.out.println("Animal is breathing");
    }
}

public class Cat implements Animal {
    @Override
    public void eat() {
        System.out.println("Cat is eating");
    }

    @Override
    public void sleep() {
        System.out.println("Cat is sleeping");
    }

    // Cat 클래스는 breathe() 메서드를 재정의하지 않아도 된다.
}

 

5. 정적 메서드(Static Method): 자바 8부터 인터페이스는 정적 메서드(static)를 가질 수 있다. 이는 클래스가 아니라 인터페이스 자체에서 호출할 수 있는 메서드.

public interface Animal {
    void eat();
    void sleep();

    static void info() {
        System.out.println("This is an Animal interface");
    }
}

public class Bird implements Animal {
    @Override
    public void eat() {
        System.out.println("Bird is eating");
    }

    @Override
    public void sleep() {
        System.out.println("Bird is sleeping");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal.info(); // This is an Animal interface
    }
}

 

사용이유와 장점

 

1. 다형성(Polymorphism)

  • 인터페이스를 사용하면 동일한 인터페이스를 구현하는 여러 클래스가 같은 방식으로 다뤄질 수 있다. 예를 들어, Animal 인터페이스를 구현한 Dog, Cat, Bird 클래스는 모두 Animal 타입으로 다룰 수 있다.
public void makeAnimalEat(Animal animal) {
    animal.eat();
}

makeAnimalEat(new Dog());
makeAnimalEat(new Cat());

 

 

2. 유연한 설계와 확장성

  • 인터페이스를 사용하면 클래스가 변경되더라도 인터페이스를 통해 의존성을 관리할 수 있어 코드의 유연성과 확장성이 증가. 새로운 기능을 추가할 때 인터페이스를 구현하기만 하면 된다.
public interface Payment {
    void pay(double amount);
}

public class CreditCardPayment implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("Paid " + amount + " using Credit Card");
    }
}

public class PayPalPayment implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("Paid " + amount + " using PayPal");
    }
}

public class PaymentProcessor {
    public void processPayment(Payment payment, double amount) {
        payment.pay(amount);
    }
}

 

3. 다중 상속

  • 자바는 클래스의 다중 상속을 지원하지 않지만, 인터페이스는 다중 상속이 가능. 한 클래스가 여러 인터페이스를 구현할 수 있어 다양한 기능을 조합할 수 있다.
public interface Drivable {
    void drive();
}

public interface Flyable {
    void fly();
}

public class FlyingCar implements Drivable, Flyable {
    @Override
    public void drive() {
        System.out.println("Driving");
    }

    @Override
    public void fly() {
        System.out.println("Flying");
    }
}

 

 

4. 코드 재사용

  • 인터페이스를 통해 동일한 기능을 여러 클래스에 제공할 수 있으므로, 코드의 재사용성을 높일 수 있다. 이는 특히 큰 프로젝트에서 유지보수성을 높이는 데 도움이 된다.

5. 테스트 용이성

  • 인터페이스를 사용하면 모의 객체(mock object)를 생성하여 테스트할 수 있어 단위 테스트가 더 쉬워진다. 이를 통해 실제 구현에 의존하지 않고도 독립적인 테스트가 가능.
public class MockPayment implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("Mock payment of " + amount);
    }
}

// 단위 테스트 코드
PaymentProcessor processor = new PaymentProcessor();
processor.processPayment(new MockPayment(), 100.0);

 

 

6. 표준화된 메서드 사용

  • 인터페이스를 사용하면 메서드 시그니처를 표준화할 수 있어, 여러 클래스가 동일한 메서드를 제공하도록 강제할 수 있다. 이는 인터페이스를 사용하는 코드의 일관성을 유지하는 데 도움.

요약

인터페이스는 다음과 같은 주요 이유와 장점 때문에 자바에서 중요하게 사용:

  • 다형성을 제공하여 다양한 클래스가 동일한 방식으로 다뤄질 수 있음
  • 코드의 유연성과 확장성을 높임
  • 다중 상속을 통한 다양한 기능 조합 가능
  • 코드 재사용성을 높이고 유지보수성을 향상시킴
  • 테스트 용이성을 제공하여 독립적인 단위 테스트 가능
  • 표준화된 메서드 시그니처를 제공하여 코드의 일관성 유지

추상클래스

추상 클래스 (Abstract Class)는 객체지향 프로그래밍에서 중요한 개념으로, 일반적인 클래스와 다르게 인스턴스를 직접 생성할 수 없다. 추상 클래스는 주로 다른 클래스들이 상속받아 사용하도록 설계.

추상 클래스의 특징과 사용 이유

  1. 추상 메서드 포함:
    • 추상 클래스는 추상 메서드를 포함할 수 있다. 추상 메서드는 선언만 되고 구현이 없는 메서드. 이를 상속받는 실제 클래스에서 반드시 구현해야 한다.
  2. 인스턴스 생성 불가:
    • 추상 클래스는 직접 객체를 생성할 수 없다. 즉, new AbstractClass()와 같이 인스턴스를 생성할 수 없다.
  3. 상속을 통한 확장:
    • 추상 클래스는 다른 클래스들에게 공통된 기능을 제공하기 위해 설계. 이 클래스들은 추상 클래스를 상속받아 추상 메서드를 구현하고 나머지 메서드들을 사용할 수 있다.
  4. 일반 메서드 포함 가능:
    • 추상 클래스에는 추상 메서드 외에도 일반 메서드, 멤버 변수, 생성자 등을 포함할 수 있다.
// 추상 클래스
abstract class Animal {
    // 추상 메서드
    public abstract void makeSound();

    // 일반 메서드
    public void sleep() {
        System.out.println("Animal sleeps");
    }
}

// 추상 클래스를 상속받는 실제 클래스
class Dog extends Animal {
    // 추상 메서드의 구현
    @Override
    public void makeSound() {
        System.out.println("Dog barks");
    }
}

class Cat extends Animal {
    // 추상 메서드의 구현
    @Override
    public void makeSound() {
        System.out.println("Cat meows");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();

        dog.makeSound();  // "Dog barks" 출력
        cat.makeSound();  // "Cat meows" 출력

        dog.sleep();      // "Animal sleeps" 출력
        cat.sleep();      // "Animal sleeps" 출력
    }
}

인터페이스 vs 클래스 상속

 

1. 다형성 (Polymorphism)

  • 인터페이스: 인터페이스는 다양한 클래스가 동일한 인터페이스를 구현하여, 같은 방식으로 다뤄질 수 있게 한다. 이는 특히 다양한 구현체를 처리할 수 있는 코드를 작성할 때 유용.
public interface Animal {
    void makeSound();
}

public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof");
    }
}

public class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();
        animal1.makeSound(); // Woof
        animal2.makeSound(); // Meow
    }
}

 

  • 상속: 클래스 상속을 통해서도 다형성을 달성할 수 있다. 자식 클래스는 부모 클래스의 메서드를 재정의하여 다형성을 구현할 수 있다.
public class Animal {
    public void makeSound() {
        System.out.println("Some sound");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof");
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Meow");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();
        animal1.makeSound(); // Woof
        animal2.makeSound(); // Meow
    }
}

 

 

2. 유연한 설계와 확장성 (Flexible Design and Extensibility)

  • 인터페이스: 인터페이스를 사용하면 여러 클래스가 동일한 인터페이스를 구현할 수 있어 새로운 기능을 추가하거나 변경할 때 기존 코드를 수정하지 않고도 확장할 수 있다.
public interface Payment {
    void process(double amount);
}

public class CreditCardPayment implements Payment {
    @Override
    public void process(double amount) {
        System.out.println("Processed " + amount + " with Credit Card");
    }
}

public class PayPalPayment implements Payment {
    @Override
    public void process(double amount) {
        System.out.println("Processed " + amount + " with PayPal");
    }
}

public class PaymentProcessor {
    public void processPayment(Payment payment, double amount) {
        payment.process(amount);
    }
}

 

  • 상속: 상속을 통해서도 클래스의 기능을 확장할 수 있다. 자식 클래스는 부모 클래스의 기능을 상속받아 사용하거나 재정의할 수 있다.
public class Payment {
    public void process(double amount) {
        System.out.println("Processed " + amount);
    }
}

public class CreditCardPayment extends Payment {
    @Override
    public void process(double amount) {
        System.out.println("Processed " + amount + " with Credit Card");
    }
}

public class PayPalPayment extends Payment {
    @Override
    public void process(double amount) {
        System.out.println("Processed " + amount + " with PayPal");
    }
}

차이점 요약

  1. 구현의 강제성:
    • 인터페이스는 구현을 강제. 인터페이스를 구현하는 클래스는 인터페이스에 정의된 모든 메서드를 구현해야 한다.
    • 상속에서는 부모 클래스의 메서드를 반드시 재정의할 필요는 없다.
  2. 다중 상속의 지원:
    • 인터페이스는 여러 개를 구현할 수 있어 다중 상속이 가능.
    • 클래스 상속은 단일 상속만 지원.
  3. 구현 독립성:
    • 인터페이스는 구현을 분리하여 각 클래스가 독립적으로 동작할 수 있도록 한다다.
    • 상속은 부모 클래스와 자식 클래스 간의 강한 결합을 유발할 수 있다.
  4. 목적:
    • 인터페이스는 클래스가 특정 행동을 수행하도록 계약을 정의하는 데 주로 사용.
    • 상속은 코드의 재사용과 클래스 계층 구조를 정의하는 데 주로 사용.

상속과 인터페이스가 주로 사용되는 부분

자바에서 상속과 인터페이스는 특정 상황에서 주로 사용되며, 겹치는 부분도 있다. 

상속이 주로 사용되는 부분

1. 코드 재사용성(Code Reusability)

  • 기존 클래스의 코드를 재사용하고, 새로운 기능을 추가하거나 기존 기능을 수정할 때 주로 사용.
public class Vehicle {
    public void start() {
        System.out.println("Vehicle started");
    }
}

public class Car extends Vehicle {
    @Override
    public void start() {
        System.out.println("Car started");
    }
}

 

2. 클래스 계층 구조(Class Hierarchy)

  • 클래스 간의 계층 구조를 정의하여 상위 클래스와 하위 클래스 간의 관계를 명확히 할 때 사용.
public class Animal {
    public void eat() {
        System.out.println("Animal is eating");
    }
}

public class Dog extends Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }
}

 

3. 공통 기능의 공유(Common Functionality Sharing)

  • 여러 클래스에서 공통으로 사용되는 기능을 부모 클래스에 정의하여 하위 클래스에서 공유할 때 사용.
public class Shape {
    public void draw() {
        System.out.println("Drawing a shape");
    }
}

public class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }
}

 

 

인터페이스가 주로 사용되는 부분

  1. 다형성(Polymorphism)
    • 다양한 클래스가 동일한 인터페이스를 구현하여, 같은 방식으로 다뤄질 수 있도록 할 때 사용.
public interface Payment {
    void process(double amount);
}

public class CreditCardPayment implements Payment {
    @Override
    public void process(double amount) {
        System.out.println("Processed " + amount + " with Credit Card");
    }
}

public class PayPalPayment implements Payment {
    @Override
    public void process(double amount) {
        System.out.println("Processed " + amount + " with PayPal");
    }
}

 

2. 구현 독립성(Implementation Independence)

  • 구현과 인터페이스를 분리하여, 다양한 구현체를 동일한 인터페이스로 다룰 수 있도록 할 때 사용.
public interface Payment {
    void process(double amount);
}

public class CreditCardPayment implements Payment {
    @Override
    public void process(double amount) {
        System.out.println("Processed " + amount + " with Credit Card");
    }
}

public class PayPalPayment implements Payment {
    @Override
    public void process(double amount) {
        System.out.println("Processed " + amount + " with PayPal");
    }
}

 

3. 다중 상속의 대체(Multiple Inheritance Alternative)

  • 자바는 클래스의 다중 상속을 지원하지 않지만, 여러 인터페이스를 구현하여 다중 상속과 유사한 효과를 낼 때 사용.
public interface Runnable {
    void run();
}

public interface Swimmable {
    void swim();
}

public class Duck implements Runnable, Swimmable {
    @Override
    public void run() {
        System.out.println("Duck is running");
    }

    @Override
    public void swim() {
        System.out.println("Duck is swimming");
    }
}

 

겹치는 부분

  1. 다형성
    • 상속과 인터페이스 모두 다형성을 지원하여, 부모 클래스나 인터페이스 타입으로 자식 클래스나 구현체를 다룰 수 있게 한다.
// 인터페이스
public interface Animal {
    void makeSound();
}

public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.makeSound(); // Woof
    }
}

// 상속
public class Animal {
    public void makeSound() {
        System.out.println("Some sound");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.makeSound(); // Woof
    }
}

 

2. 유연한 설계와 확장성

  • 상속과 인터페이스 모두 코드의 유연성을 높이고, 새로운 기능을 쉽게 추가할 수 있도록 설계를 유연하게 만든다.

요약

  • 상속은 주로 코드 재사용, 클래스 계층 구조 정의, 공통 기능 공유에 사용.
  • 인터페이스는 주로 다형성, 구현 독립성, 다중 상속의 대체에 사용.
  • 겹치는 부분으로는 다형성 지원과 유연한 설계와 확장성이 있다.

추상메서드와 인터페이스의 차이점 요약

특성 추상  클래스인터페이스
정의 abstract 키워드 사용 interface 키워드 사용
메서드 추상 메서드와 구현된 메서드 모두 가짐 기본적으로 추상 메서드, Java 8 이후 디폴트 및 static 메서드 지원
변수 인스턴스 변수, 클래스 변수 가질 수 있음 상수만 가질 수 있음 (public static final)
상속/구현 상속을 통해 확장 구현을 통해 확장
다중 상속 다중 상속 지원하지 않음 다중 상속 지원
생성자 생성자 가질 수 있음 생성자 가질 수 없음
목적 공통 기능의 제공 및 코드 재사용 메서드의 계약 정의 및 다중 상속 지원

 

728x90
반응형