자바(JAVA)

자바 - 객체 지향(2) : 상속, 생성자, 인터페이스

BlueNoa 2023. 9. 14. 20:49
728x90
반응형

<목차>

 

 


• 상속(Inheritance)

상속은 객체 지향 프로그래밍의 핵심 개념 중 하나로, 클래스 간의 관계를 나타내며 코드의 재사용을 활발히 할 수 있는 방법 중 하나다. 자식 클래스가 부모 클래스로부터 특성(멤버 변수와 메서드)을 물려받아 사용할 수 있도록 하는 메커니즘이다. 

 

상속의 특징

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

- 상속은 클래스 간의 계층 구조를 형성한다. 부모 클래스(슈퍼 클래스)와 자식 클래스(서브 클래스) 간의 관계를 성립한다.

- 부모 클래스는 일반적인 특성(멤버 변수 등)과 동작(메서드)을 정의하며, 자식 클래스부모 클래스의 특성을 상속하여       추가적인 특성을 정의할 수 있다.

- 자식 클래스는 하나의 부모 클래스를 상속 받을 수 있고, 다중 상속은 지원하지 않는다.

 

2. extends 키워드

자바에서 상속을 구현하기 위해 'extends' 키워드를 사용한다. 자식 클래스를 정의할 때 이 키워드를 사용하여 부모 클래스를 지정할 수 있다.

 

3. super 키워드

자식 클래스에서 부모 클래스의 멤버에 접근할 때 'super' 키워드를 사용한다. 이를 통해 부모 클래스의 생성자, 메서드, 필드에 접근할 수 있다.

 

4. 다형성(Polymorphism)

상속을 통해 다형성을 가능하게 한다. 이는 자식 클래스 객체를 부모 클래스의 참조 변수에 할당할 수 있다는 것을 의미한다. 이를 통해 자식 클래스 객체를 동일한 부모 클래스 타입으로 다룰 수 있다. 대표적인 예로 메서드 오버로딩 오버라이딩 그리고 인터페이스와 추상 클래스가 다형성이 예가 되겠다.

 

5. 접근 제어자(Access Modifiers)

부모 클래스의 멤버 변수와 메서드가 자식 클래스로 상속될 때, 접근 제어자에 따라 접근 권한이 결정된다. 'private' 멤버는 상속되지 않고, 'protected', 'public', 'default' 등의 멤버는 상속된다.

[참고 - https://bluenoa.tistory.com/72#text1]

 

자바 - 접근 제어자(Access Modifier)

접근 제어자(Access Modifier)란? 접근 제어자 예시 • 접근 제어자(Access Modifier)란? 접근 제어자란 클래스, 인터페이스, 변수, 메서드 등 접근 권한을 제어하는 데 사용되는 키워드이다. 접근 제어자

bluenoa.tistory.com

 

<예시(접은글) - 부모 클래스와 자식 클래스 관계 및 오버라이딩 / 오버로딩>

오버로딩과 오버라이딩은 파이썬 게시글에서도 다룬 적이 있다. 즉, 다른 언어에서도 사용하는 개념이기 때문에 기억하고 있으면 다른 언어를 배울 때도 유용하다.

 

오버라이딩(Overrideing) : 부모 클래스의 메서드(또는 함수)를 상속 받은 자식 클래스가 재정의(덮어쓰기 및 기능 추가)를 하는 행위

 

오버로딩(Overloading) : 동일한 이름의 메서드가 존재하지만 입력 값을 받음으로써 똑같은 이름의 메서드를 만들 수 있다.

예시 보기
// 부모 클래스
class Animal {
    String name;

    public Animal(String name) {
        this.name = name;
    }

    public void speak() {
        System.out.println(name + "이/가 울음소리를 냅니다.");
    }
}

// 자식 클래스 1번
class Wolf extends Animal {
    public Wolf(String name) {
        super(name);
    }
    // 오버라이딩
    public void speak() {
        System.out.println(name + "이/가 하울링을 한다.");
    }
    public void behavior() {
        System.out.println(name + "이/가 달을 바라본다.");
    }
}

// 자식 클래스 2번
class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    // 오버라이딩
    public void speak() {
        System.out.println(name + "이/가 멍멍 짖습니다.");
    }

    public void dig() {
        System.out.println(name + "이/가 땅을 팝니다");
    }

    // 메서드 오버로딩 : 입력값 유/무를 기준으로 동일한 메서드명으로 생성한다.
    public void dig(int time) {
        System.out.println(name + "이/가 " + time +"시간 땅을 팝니다.");
    }


}

public class sample {
    public static void main (String[] args) {
        Wolf wolf = new Wolf("회색늑대");
        Dog dog = new Dog("바둑");
        // 울음 소리
        wolf.speak(); dog.speak();
        // 고유 행동
        wolf.behavior(); dog.dig(); dog.dig(3);
        
        // 다형성을 이용한 부모 클래스 참조로 자식 클래스 객체 사용
        Animal animal_1 = new Wolf("붉은 늑대");
        Animal animal_2 = new Dog("하루");
        // 울음 소리
        animal_1.speak(); animal_2.speak();
        // 고유 행동 - 오류 발생 : 부모 클래스 참조로는 자식 클래스의 오버라이딩 메서드에는 접근 불가
//        animal_1.behavior();
//        animal_2.dig()
    }
}

<결과>

회색늑대이/가 하울링을 한다.
바둑이/가 멍멍 짖습니다.
회색늑대이/가 달을 바라본다.
바둑이/가 땅을 팝니다
바둑이/가 3시간 땅을 팝니다.
붉은 늑대이/가 하울링을 한다.
하루이/가 멍멍 짖습니다.
반응형

• 생성자(Constructor)

자바의 생성자(Constructor)는 클래스의 인스턴스(객체)를 초기화하는 특별한 메서드다. 객체가 생성될 때 자동으로 호출되며, 주로 객체의 초기 상태를 설정하는 데 사용한다.

 

생성자의 특징

- 생성자는 클래스의 이름과 동일한 이름을 갖는다. void도 사용하지 않는다.

- 생성자는 반환값을 가지지 않으며, 반환 타입을 지정하지 않는다.

- 객체가 생성될 때 자동으로 호출되며, 일반적으로 'new' 키워드를 사용하여 호출한다.

- 여러 개의 생성자를 정의할 수 있다. 예를 들어 오버로딩(Overloading)을 사용하여 매개 변수의 수나 타입이 다른 생성자를 여러 개 정의할 수 있다.

 

기본 생성자

클래스가 어떤 생성자도 정의하지 않았을 때, 자바 컴파일러는 자동으로 기본 생성자를 추가한다. 기본 생성자는 매개변수를 가지지 않고 객체를 초기화한다.

// case 1
public class MyClass {
	// 아무것도 적혀있지 않다. 즉 자동으로 기본 생성자가 정의된다.
}

// case 2
public class MyClass {
	MyClass() { // 기본 생정자
    }
}

 

매개 변수를 갖는 생성자

객체를 생성할 때 초기값을 제공하기 위해 매개변수를 갖는 생성자를 정의할 수 있다.

위에서 상속에 대한 예시에도 사용했었다.

public class student {
    private String name;
    private int age;

    // 매개 변수를 받는 생성자
    public student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

 

생성자 호출

객체를 생성할 때 생성자를 호출하게 된다. 생성자는 'new' 키워드로 호출하며, 필요한 인수(매개 변수)를 전달한다.

student std = new student("홍길동", 17); // 생성자 호출

 

생성자의 오버로딩(Overloading)

클래스 내에 여러 개의 생성자를 정의하여 여러 초기화 값을 지정할 수 있다. 생성자의 시그니처(매개 변수의 타입)가 다르면 여러 개의 생성자를 정의할 수 있다.

class Phone {
    private String model;
    private String year;

    // 생성자1
    public Phone(String model) {
        this.model = model;
    }

    // 생성자2 - 오버로딩
    public Phone(String model, String year) {
        this.model = model;
        this.year = year;
    }

    void info() {
        if (year == null) {
            System.out.println("해당 기기의 기종은 " + model + ", 제조일자는 정보가 없습니다.");
        } else {
            System.out.println("해당 기기의 기종은 " + model + ", 제조일자는 " + year + "년 입니다.");
        }
    }
}


public class sample {
    public static void main (String[] args) {
        Phone phone1 = new Phone("갤럭시s23");
        Phone phone2 = new Phone("아이폰14 pro", "2023");

        phone1.info(); phone2.info();
    }
}

<결과>

해당 기기의 기종은 갤럭시s23, 제조일자는 정보가 없습니다.
해당 기기의 기종은 아이폰14 pro, 제조일자는 2023년 입니다.
728x90

• 인터페이스(Interface)

인터페이스는 클래스와 마찬가지로 객체 지향 프로그래밍의 중요한 개념 중 하나다. 대체로 추상적인 데이터 타입을 나타내는데 사용된다. 또 인터페이스는 상속과 다르게 인터페이스 메서드를 반드시 구현해야 된다는 강제성을 갖고 있다.

이는 상속 받는 클래스와는 관계없이 인터페이스를 사용하면 독립적으로 메서드 구현이 가능하다는 점이다.

 

※ implements 키워드 : 인터페이스를 구현하는 클래스에서 사용된다. 해당 키워드를 사용하면 클래스가 특정 인터페이스의 규약(메서드 선언)을 따르도록 강제할 수 있다. 클래스가 인터페이스를 구현하려면 인터페이스에 정의된 모든 추상 메서드를 구체적으로 구현해야 한다.

 

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

 

인터페이스의 특징

1. 추상 메서드(Abstract Method)

추상 메서드는 구현 코드가 없고 메서드의 시그니처(메서드 이름, 매개 변수, 반환 타입)만 선언된다. 또 추상 메서드는 하위 클래스에서 반드시 구현(Implement)되어야 한다. 즉 상속 받는 자식 클래스(하위 클래스)에서 반드시 '@Override'를 사용하여 재정의해야 한다. 이는 파이썬에서도 동일한 내용이다.

[참고(파이썬에서도 비슷하다.) - https://bluenoa.tistory.com/68]

 

파이썬 - 클래스(3)

2023.01.25 - [파이썬] - 파이썬 - 클래스(1) 2023.02.08 - [파이썬] - 파이썬 - 클래스(2) 이전 게시글과 이어지는 내용이므로 참고 •추상 메서드 추상 메소드(abstractmethod)는 스스로는 객체 생성이 불가능

bluenoa.tistory.com

<예시 - 추상 메서드> : 반드시 재정의 해야 된다.

더보기
// 추상 클래스 Monster
abstract class Monster {
    String name;

    public Monster(String name) {
        this.name = name;
    }
    // 추상 메서드
    public abstract void Weakness();

    // 일반 메서드
    public void behavior() {
        System.out.println(name + "이/가 나타났습니다.");
    }
}

// Monster를 상속하는 하위 클래스1
class Vampire extends Monster {
    public Vampire(String name) {
        super(name);
    }

    // 추상 메서드 구현
    @Override
    public void Weakness() {
        System.out.println(name + "이/가 십자가에 놀라 도망갔습니다.");
    }
}

// 클래스2
class Werewolf extends Monster {
    public Werewolf(String name) {
        super(name);
    }

    // 추상 메서드 구현
    @Override
    public void Weakness() {
        System.out.println(name + "이/가 은을 보고 도망갔습니다.");
    }
}

public class sample {
    public static void main (String[] args) {
        Monster vp = new Vampire("뱀파이어");
        Monster wf = new Werewolf("늑대인간");

        vp.behavior(); wf.behavior();
        vp.Weakness(); wf.Weakness();
    }
}

 

2. 상수(Constants)

인터페이스는 상수를 정의할 수 있다. 정의된 상수는 'public', 'static', 'final'로 설정된다. 인터페이스에서 정의된 상수는 변경할 수 없으며, 모든 클래스에서 공유한다. 또 상수는 선언과 동시에 값을 할당해야 하며, 인터페이스 내에서만 유효하다.

 

<예시 - 인터페이스 상수>

더보기
// 인터페이스 정의
interface Shape {
    // 인터페이스 상수 정의
    double PI = 3.141592;

    // 추상 메서드 정의
    double calculaterArea();
}

// 원의 넓이
class Circle implements Shape {
    private double radius;

    public Circle(double radius) { // 기본 생성자
        this.radius = radius;
    }

    @Override // 오버라이딩
    public double calculaterArea() {
        return PI * radius * radius;
    }
}

// 직사각형의 넓이
class Retangle implements Shape {
    private double width;
    private double height;

    public Retangle(double width, double height) {
        this.width = width; this.height = height;
    }

    @Override
    public double calculaterArea() {
        return width * height;
    }
}


public class sample {
    public static void main (String[] args) {
        Circle circle = new Circle(10);
        Retangle retangle = new Retangle(20, 30);
        
        // 인터페이스 상수 사용
        System.out.println("Pi : " + Shape.PI);
        System.out.println("원의 넓이 : " + circle.calculaterArea());
        System.out.println("직사각형의 넓이 : " + retangle.calculaterArea());
    }
}

<결과>

Pi : 3.141592
원의 넓이 : 314.1592
직사각형의 넓이 : 600.0

 

3. 다중 상속

클래스는 다중 상속을 지원하지 않지만, 인터페이스는 다중 상속을 지원한다. 인터페이스의 다중 상속은 하나의 클래스가 여러 개의 인터페이스를 구현할 수 있음을 의미한다. 이렇게 하면 하나의 클래스가 다양한 기능을 갖출 수 있으며, 객체 지향 프로그래밍의 유연성과 재사용성을 높일 수 있다. 다만 주의해야 되는 점이 있는데 서로 다른 인터페이스에서 정의한 메서드가 이름이 같은 경우 충돌을 피하기 위해 어떤 메서드를 호출할지 잘 선택해야 한다.

 

<예시 - 다중 상속>

더보기
interface Write {
   void Writing();
}

interface Erase {
    void Erasing();
}

class Writer implements Write, Erase {
    @Override
    public void Writing() {
        System.out.println("작가가 글을 쓰고 있다.");
    }

    @Override
    public void Erasing() {
        System.out.println("작가가 글을 지우고 있다.");
    }
}

public class sample {
    public static void main (String[] args) {
        Writer writer = new Writer();

        writer.Writing(); writer.Erasing();
    }
}

<결과>

작가가 글을 쓰고 있다.
작가가 글을 지우고 있다.

 

4. 인터페이스 간 상속

하나의 인터페이스가 다른 인터페이스의 메서드를 상속받는 것이 가능하다. 인터페이스 간의 상속을 통해 관련된 공통의 메서드를 공유하거나, 더 큰 기능을 갖는 인터페이스를 정의할 수 있다. 인터페이스 간의 상속은 'extends' 키워드를 사용한다.

 

<예시 - 인터페이스 간 상속>

더보기
// 모양을 정의하는 인터페이스
interface Phone {
    void model();
}

// 색상 인터페이스는 모양 인터페이스를 상속
interface Color extends Phone {
    void setColor(String color);
}

// 클래스1
class Galaxy implements Color {
    private String color;

    @Override
    public void model() {
        System.out.println("갤럭시s23을 원합니다.");
    }

    @Override
    public void setColor(String color) {
        this.color = color;
        System.out.println(color + " 색상을 원합니다.");
    }
}

// 클래스2
class iPhone implements Color {
    private String color;

    @Override
    public void model() {
        System.out.println("아이폰15 pro를 원합니다.");
    }

    @Override
    public void setColor(String color) {
        this.color = color;
        System.out.println(color + " 색상을 원합니다.");
    }
}

public class sample {
    public static void main (String[] args) {
        Galaxy galaxy = new Galaxy();
        iPhone iPhone = new iPhone();

        galaxy.model(); galaxy.setColor("보라색");
        iPhone.model(); iPhone.setColor("레드");
    }
}

<결과>

갤럭시s23을 원합니다.
보라색 색상을 원합니다.
아이폰15 pro를 원합니다.
레드 색상을 원합니다.

 

5. 디폴트 메서드

디폴트 메서드는 Java8 버전 부터 지원한 인터페이스의 새로운 기능이다. 해당 기능은 인터페이스 내에서 구현되는 메서드로, 기존의 인터페이스를 수정하지 않고 새로운 메서드를 추가할 수 있게 되었다. 즉, 오버라이드 하지 않을 수 있게 되었다. 

<예제 - 디폴트 메서드> : 인터페이스 간 상속의 예제를 재사용

더보기
// 모양을 정의하는 인터페이스
interface Phone {
    void model();
}

// 색상 인터페이스는 모양 인터페이스를 상속
interface Color extends Phone {
    void setColor(String color);
    
    // 기존 인터페이스에 디폴트 메서드 추가
    default void storage() {
        System.out.println("용량은 256gb로 하고 싶어요.");
    }
}

// 클래스1
class Galaxy implements Color {
    private String color;

    @Override
    public void model() {
        System.out.println("갤럭시s23을 원합니다.");
    }

    @Override
    public void setColor(String color) {
        this.color = color;
        System.out.println(color + " 색상을 원합니다.");
    }
}

// 클래스2
class iPhone implements Color {
    private String color;

    @Override
    public void model() {
        System.out.println("아이폰15 pro를 원합니다.");
    }

    @Override
    public void setColor(String color) {
        this.color = color;
        System.out.println(color + " 색상을 원합니다.");
    }
}

public class sample {
    public static void main (String[] args) {
        Galaxy galaxy = new Galaxy();
        iPhone iPhone = new iPhone();


        galaxy.model(); galaxy.setColor("보라색"); galaxy.storage();
        iPhone.model(); iPhone.setColor("레드");
    }
}

 

 

 

 

 

728x90
반응형