[Java] 상속과 다형성

artp·2025년 1월 21일
0

java

목록 보기
10/32
post-thumbnail

8. 상속과 다형성

상속과 다형성은 객체지향 프로그래밍의 핵심 개념으로, 코드의 재사용성유연성을 극대화합니다.

  • 상속: 기존 클래스를 기반으로 새로운 클래스를 작성하여 코드 재사용성을 높입니다.
  • 다형성: 하나의 객체가 여러 가지 형태를 가지는 성질로, 유연한 코드 작성이 가능합니다.

8.1 상속

8.1.1 상속이란

상속은 기존 클래스(부모 클래스)를 재사용하여 새로운 클래스(자식 클래스)를 정의하는 방법입니다.
자식 클래스는 부모 클래스의 필드와 메서드를 상속받아 사용할 수 있으며, 필요에 따라 추가하거나 수정할 수 있습니다.

부모 클래스를 상위 클래스 또는 슈퍼 클래스(super class), 자식 클래스를 하위 클래스 또는 서브 클래스(sub class)라고도 합니다.

상속 관계를 표시하는 방법은 간단합니다. 상속받는 자식 클래스 뒤에 extends 키워드로 부모 클래스를 연결하면 됩니다.

class 자식 클래스명 extends 부모 클래스명 {
	// 클래스 본문
}

예제

// 부모 클래스
public class Parent {
	String name;
    
    public void sayHello() {
    	System.out.println("Hello from Parent.");
    }
}

// 자식 클래스
public class Child extends Parent {
	// Child는 Parent의 필드와 메서드를 상속받습니다.
}

8.1.2 실습: 클래스 상속하기

예제

public class Parent {
	public void sayHello() {
    	System.out.println("Hello from Parent.");
    }
}

public class Child extends Parent {
	public void sayHello() {
    	System.out.println("Hello from Child.");
    }
}

public class Main {
	public static void main(String[] args) {
    	Parent parent = new Parent();
        Child child = new Child();
        
        parent.sayHello(); // 출력: Hello from Parent.
        child.sayHello(); // 출력: Hello from Child.
    }
}

8.2 다형성

다형성은 객체지향 프로그래밍(OOP)의 핵심 개념 중 하나로, 같은 이름을 가진 메서드나 연산이 상황에 따라 다르게 동작할 수 있는 특성을 의미합니다. 쉽게 말해, 하나의 기능을 여러 방식으로 구현할 수 있는 것을 뜻합니다. 예를 들어, 같은 이름의 메서드라도 입력값에 따라 다른 결과를 내거나(오버로딩), 부모 클래스에서 정의한 메서드를 자식 클래스에서 변경하여 사용(오버라이딩)할 수도 있습니다.

8.2.1 메서드 오버라이딩

자식 클래스가 부모 클래스를 상속받으면 메서드를 그대로 사용합니다. 그런데 자식 클래스에서 다른 동작을 수행하도록 메서드를 변형해서 사용할 수도 있습니다. 즉, 상속받은 메서드를 재정의할 수 있으며, 이를 메서드 오버라이딩이라고 합니다.

  • 부모 클래스에서 상속받은 메서드자식 클래스에서 재정의하는 것입니다.
  • 자식 클래스의 특정 동작을 부모 클래스와 다르게 구현할 수 있습니다.
  • @Override 애너테이션으로 오버라이딩 여부를 명시합니다.
  • 메서드명, 반환형, 매개변수의 개수와 순서까지 모두 같고 동작만 다른 메서드를 자식 클래스에 정의합니다.

오버라이딩상속 관계에서 일어납니다.

예제

public class Parent {
	public void hello() {
    	System.out.println("Hello from Parent.");
    }
}

public class Child extends Parent {
	@Override
    public void hello() {
    	System.out.println("Hello from Child.");
    }
}

8.2.2 메서드 오버로딩

메서드명은 같지만 매개변수의 개수와 순서, 자료형이 다른 메서드를 같은 클래스 안에 여러 개 정의하는 것입니다.

  • 같은 이름의 메서드를 매개변수의 타입이나 개수를 다르게 하여 여러 개 정의하는 것입니다.
  • 컴파일 시점에 호출할 메서드가 결정됩니다.

오버로딩한 클래스 안에서 일어납니다.

예제

public class Calculator {
	public int add(int a, int b) {
    	return a + b;
    }
    
    public double add(double a, double b) {
    	return a + b;
    }
}

8.2.3 형변환

형변환이란 상속 관계에서 객체 타입을 변환하는 것을 의미합니다.

업캐스팅(upcasting)

자식 클래스의 객체를 부모 클래스형으로 변환하는 것을 말합니다.
이는 명시적으로 형변환하지 않아도 자동으로 이루어집니다.

Parent parent = new Child(); // 업캐스팅

업캐스팅의 특징

1. 자동 형변환

  • 업캐스팅은 명시적으로 형변환하지 않아도 자동으로 수행됩니다.
  • 부모 클래스의 참조 변수는 자식 클래스의 객체를 참조할 수 있습니다.

2. 제한된 접근

  • 업캐스팅된 참조 변수로는 부모 클래스에 선언된 필드와 메서드만 접근할 수 있습니다.
  • 자식 클래스에 새로 추가된 필드나 메서드에는 접근할 수 없습니다.

3. 오버라이딩된 메서드

  • 자식 클래스에서 부모 클래스의 메서드를 오버라이딩한 경우, 업캐스팅된 참조 변수를 호출하면 오버라이딩된 메서드가 실행됩니다.

예제

public class Parent {
	public void hello() {
    	System.out.println("Hello from Parent.");
    }
}

public class Child extends Parent {
	@Override
    public void hello() {
    	System.out.println("Hello from Child.");
    }
    
    public void play() {
    	System.out.println("Child is playing.");
    }
}

public class Main {
	public static void main(String[] args) {
    	Parent parent = new Child(); // 업캐스팅
        parent.hello(); // 오버라이딩된 메서드 실행: Hello from Child.
        
        // parent.play(); // 오류: 부모 클래스에 정의되지 않은 메서드
    }
}

출력

Hello from Child.

업캐스팅 활용

1. 다형성

  • 업캐스팅은 부모 클래스의 참조 변수를 사용하여 다양한 자식 클래스의 객체를 처리할 수 있게 합니다.
  • 예를 들어, 여러 자식 클래스 객체를 공통된 부모 클래스 타입으로 관리할 수 있습니다.

2. 코드 유연성

  • 부모 클래스형 변수에 다양한 자식 클래스 객체를 참조시켜, 동적이고 유연한 코드를 작성할 수 있습니다.

다운캐스팅(downcasting)

다운캐스팅은 부모 클래스형으로 참조된 객체를 자식 클래스형으로 형변환하는 것을 말합니다.
이는 업캐스팅된 객체를 명시적으로 변환하여 자식클래스에 정의된 필드와 메서드에 접근하기 위해 사용됩니다.

다운캐스팅의 특징

1. 명시적 형변환 필요

  • 다운캐스팅은 명시적으로 형변환을 작성해야 합니다.
  • 형식:
    자식클래스 변수명 = (자식클래스) 부모클래스 변수명;

2. 형변환 가능 여부 확인

  • 다운캐스팅이 가능한지 확인하려면 instanceof 연산자를 사용할 수 있습니다.
  • 다운캐스팅은 부모 클래스형 참조 변수가 실제로 자식 클래스 객체를 참조하고 있어야만 가능합니다. 그렇지 않으면 ClassCastException 예외가 발생합니다.

3. 자식 클래스의 멤버에 접근 가능

  • 다운캐스팅을 통해 자식 클래스에 정의된 필드와 메서드에 접근할 수 있습니다.

예제

public class Parent {
	public void hello() {
    	System.out.println("Hello from Parent.");
    }
}

public class Child extends Parent {
	public void play() {
    	System.out.println("Child is playing.");
    }
}

public class Main {
	public static void main(String[] args) {
    	Parent parent = new Child(); // 업캐스팅
        parent.hello(); // 출력: Hello from Parent.
        
        // 다운캐스팅
        Child child = (Child) parent;
        child.play(); // 출력: Child is playing;
    }
}

출력

Hello from Parent.
Child is playing.

instanceof를 사용한 예제

다운캐스팅 시, 부모 클래스형 참조 변수가 실제로 자식 클래스 객체를 참조하지 않으면 ClassCastException이 발생합니다. 이를 방지하려면 instanceof 연산자로 다운캐스팅 가능 여부를 확인하면 됩니다.

public class Main {
	public static void main(String[] args) {
    	Parent parent = new Parent();
        
        // 안전한 다운캐스팅
        if (parent instanceof Child) {
        	Child child = (Child) parent;
            child.play(); // 실행되지 않음
        } else {
        	System.out.println("Cannot cast Parent to Child.");
        }
    }
}

출력

Cannot cast Parent to Child.

다운캐스팅이 필요한 이유

1. 자식 클래스의 고유 기능 접근

  • 부모 클래스형 변수로는 자식 클래스에 추가된 메서드나 필드에 접근할 수 없습니다.
  • 다운캐스팅을 통해 자식 클래스의 고유한 기능을 사용할 수 있습니다.

2. 다형성 구현

  • 업캐스팅으로 부모 클래스형 참조 변수를 사용해 다양한 객체를 처리할 수 있지만, 특정 자식 클래스의 기능이 필요할 때 다운캐스팅이 필요합니다.

8.2.4 super

super부모 클래스의 멤버(필드, 메서드, 생성자)를 참조하거나 호출할 때 사용되는 특별한 키워드입니다.
주로 상속 관계에서 부모 클래스의 기능을 활용하거나 재정의된 메서드에서 부모 클래스의 동작을 유지할 때 사용됩니다.

super의 주요 사용 목적

1. 부모 클래스의 필드 참조

  • 부모 클래스와 자식 클래스가 같은 이름의 필드를 가질 때, super를 사용하면 부모 클래스의 필드를 참조할 수 있습니다.

예제

public class Parent {
	String name = "Parent";
}

public class Child extends Parent {
	String name = "Child";
    
    public void showName() {
    	System.out.println("Child's name: " + name); // 자식 클래스의 name 필드
        System.out.println("Parent's name: " + super.name); // 부모 클래스의 name 필드
    }
}

public class Main {
	public static void main(String[] args) {
    	Child child = new Child();
        child.showName();
    }
}

출력

Child's name: Child
Parent's name: Parent

2. 부모 클래스의 메서드 호출

  • 자식 클래스에서 부모 클래스의 메서드를 오버라이딩한 경우, super를 사용하면 부모 클래스의 원래 메서드를 호출할 수 있습니다.

예제

public class Parent {
	public void hello() {
    	System.out.println("Hello from Parent.");
    }
}

public class Child extends Parent {
	@Override
    public void hello() {
    	System.out.println("Hello from Child.");
    }
    
    public void helloParent() {
    	super.hello(); // 부모 클래스의 hello 메서드 호출
    }
}

public class Main {
	public static void main(Stirng[] args) {
    	Child child = new Child();
        child.hello(); // 출력: Hello from Child.
        child.helloParent(); // 출력: Hello from Parent.
    }
}

출력

Hello from Child.
Hello from Parent.

3. 부모 클래스의 생성자 호출

  • super 키워드를 사용하면 부모 클래스의 생성자를 호출할 수 있습니다.
  • 생성자에서 첫 줄에만 사용할 수 있으며, 부모 클래스 생성자를 명시적으로 호출하지 않으면 기본 생성자가 자동으로 호출됩니다.

예제

public class Parent {
	public Parent(String message) {
    	System.out.println("Parent constructor: " + message);
    }
}

public class Child extends Parent {
	public Child(String message) {
    	super(message); // 부모 클래스의 생성자 호출
        System.out.println("Child constructor: " + message);
    }
}

public class Main {
	public static void main(String[] args) {
    	Child child = new Child("Hello!");
    }
}

출력

Parent constructor: Hello!
Child constructor: Hello!

super 사용 시 주의사항

  • 생성자에서 super는 반드시 첫 줄에 작성해야 합니다.
  • 부모 클래스에 기본 생성자가 없고, 다른 생성자를 정의했다면, super를 명시적으로 호출해야 합니다.
  • super는 부모 클래스의 멤버에만 접근 가능하며, 조부모 클래스의 멤버에는 직접 접근할 수 없습니다.

super 키워드 사용 조건

1. 상속 관계에서만 사용 가능

  • 부모 클래스의 멤버(필드, 메서드, 생성자)에 접근하려면 상속 관계가 있어야 합니다.
  • 상속 관계가 없는 클래스에서 super를 사용하려고 하면 컴파일 오류가 발생합니다.

2. 자식 클래스 내에서만 사용 가능

  • super는 자식 클래스의 코드에서 부모 클래스의 멤버를 참조할 때만 사용할 수 있습니다.

8.3 추상 클래스

상속에서 중요한 개념 중 하나로 추상 클래스가 있습니다.
추상은 '여러 가지 사물이나 개념에서 공통되는 특성이나 속성 따위를 추출해 파악하는 작용(여러 개별적인 것들에서 공통적인 본질을 뽑아내어 하나의 개념으로 정리하는 과정)'입니다. 그리고 추상적이란 '어떤 사물이 일정한 형태와 성질을 갖추고 있지 않은 것(구체적인 형태가 없거나, 개념적으로만 존재하는 것)'을 뜻합니다.
단어 뜻처럼 추상 클래스일반 클래스의 공통 부분을 추출해서 만든 완전하지 않은 클래스입니다. 추상 클래스는 구체적인 객체를 만들기 위한 설계도 역할을 하며, 이를 상속받아 자식 클래스에서 구체적인 동작을 정의하게 됩니다.

8.3.1 추상 클래스란

추상 클래스의 특징

1. 추상 클래스 선언

  • abstract 키워드를 사용하여 선언합니다.
  • 예: public abstract class ClassName { ... }

2. 추상 메서드 포함

  • 추상 메서드는 메서드의 선언부만 작성하고, 구현(내용)은 하지 않은 메서드입니다.
  • 자식 클래스에서 반드시 오버라이딩하여 구체적인 동작을 정의해야 합니다.
  • abstract 키워드를 사용하여 선언합니다.
  • 예: public abstract void methodName();

3. 객체 생성 불가

  • 추상 클래스는 new 키워드로 직접 객체를 생성할 수 없습니다.
  • 객체는 추상 클래스를 상속받은 자식 클래스에서만 생성할 수 있습니다.

4. 일반 메서드 포함 가능

  • 추상 클래스는 추상 메서드뿐만 아니라 일반 메서드도 포함할 수 있습니다.
  • 공통적인 동작은 부모 클래스에서 정의하고, 각 클래스별로 특화된 동작은 자식 클래스에서 구현할 수 있습니다.

추상 클래스의 구조

public abstract class Animal {
	// 필드
    String name;
    
    // 일반 메서드
    public void sleep() {
    	System.out.println(name + "is sleeping.");
    }
    
    // 추상 메서드
    public abstract void makeSound(); // 내용 없음
}

선언

선언은 변수, 메서드, 클래스 등의 이름과 타입을 정의하는 것입니다. 선언 자체로 어떤 동작이 수행되지는 않습니다. 선언은 변수, 메섣, 클래스 등의 정보를 컴파일러에게 알려주는 역할만 합니다.

구현

구현은 선언한 메서드, 클래스가 실제 수행하는 기능을 정의하는 것입니다. 어떤 동작을 하는지 코드를 작성하는 부분입니다. 변수를 초기화하는 것도 일종의 구현 행위입니다.

8.3.2 실습: 추상 클래스 상속하기

1. 추상 클래스 정의

// 추상 클래스 Animal
public abstract class Animal {
	String name;
    
    // 일반 메서드
    public void sleep() {
    	System.out.println(name + " is sleeping.");
    }
    
    // 추상 메서드
    public abstract void makeSound(); // 구현하지 않음
}

2. 자식 클래스에서 상속받아 구현

// Dog 클래스
public class Dog extends Animal {
	// 추상 메서드는 반드시 구현해야 함
	@Override
    public void makeSound() {
    	System.out.println("멍멍 왈왈!");
    }
}

// Cat 클래스
public class Cat extends Animal {
	// 추상 메서드는 반드시 구현해야 함
	@Override
    public void makeSound() {
    	System.out.println("야옹~");
    }
}

3. 추상 클래스 사용

public class Main {
	public static void main(String[] args) {
    	// Animal animal = new Animal(); // 오류: 추상 클래스는 직접 객체 생성 불가
        
        // 자식 클래스 객체 생성
        Animal dog = new Dog();
        dog.name = "군밤";
        dog.sleep(); // 출력: 군밤 is sleeping.
        dog.makeSound; // 출력: 멍멍 왈왈!
        
        Animal cat = new Cat();
        cat.name = "도도";
        cat.sleep(); // 출력: 도도 is sleeping.
        cat.makeSound; // 출력: 야옹~
    }
}

4. 출력 결과

군밤 is sleeping.
멍멍 왈왈!
도도 is sleeping.
야옹~

추상 클래스가 직접 객체를 생성하지 못하는 이유

추상 클래스가 직접 객체를 생성하지 못하는 이유는 추상 클래스가 완전하지 않은 클래스이기 때문입니다. 추상 클래스는 설계도 역할을 하며, 일부 메서드는 구현되지 않은 추상 메서드로 남아 있습니다. 객체 생성은 구체적인 동작이 모두 정의된 클래스에서만 가능합니다.

1. 추상 메서드 (미완성된 메서드)

  • 추상 클래스는 하나 이상의 추상 메서드를 포함할 수 있습니다(포함하지 않을 수도 있습니다).
  • 추상 메서드는 메서드의 선언만 있고 구현(본문, 내용)이 없기 때문에, 객체가 생성되면 해당 메서드를 호출했을 때 동작을 정의할 수 없습니다.

예제

public abstract class Animal {
	public abstract void makeSound(); // 추상 메서드
}

public class Main {
	public static void main(String[] args) {
    	// Animal animal = new Animal(); // 오류: 추상 클래스는 객체를 생성할 수 없음
    }
}
  • makeSound 메서드는 구현되지 않았으므로, Animal 객체를 생성하고 메서드를 호출하면 어떻게 동작해야 할지 알 수 없습니다.

2. 추상 클래스는 설계도 역할

  • 추상 클래스는 공통적인 속성과 동작을 정의하고, 이를 자식 클래스가 구체적인 동작을 구현하도록 설계되었습니다.
  • 추상 클래스는 완전한 객체를 생성하기 위한 것이 아니라, 자식 클래스가 설계 기반으로 삼을 틀을 제공하는 것입니다.
  • 추상 클래스는 "공통 설계도를 제공하는 템플릿"입니다.
  • 추상 클래스(설계도)를 기반으로 자식 클래스(구체적인 객체)를 만들어야 합니다.

3. 상속을 통한 구체화 강제

  • 추상 클래스는 자식 클래스가 반드시 구현해야 할 메서드(추상 메서드)를 정의하여, 모든 자식 클래스가 특정 동작을 제공하도록 강제합니다.
  • 이를 통해 코드의 일관성을 유지하고, 부모 클래스의 규칙을 따르게 합니다.

예제

public abstract class Animal {
	public abstract void makeSound(); // 추상 메서드
}

public class Dog extends Animal {
	@Override
    public void makeSoune() {
    	System.out.println("멍멍!");
    }
}

public clas Main {
	public static void main(String[] args) {
    	Animal dog = new Dog(); // 자식 클래스 객체 생성
        dog.makeSound(); // 출력: 멍멍!
    }
}
  • Animal은 추상 클래스이므로 객체를 생성할 수 없습니다.
  • Dog 클래스에서 makeSound를 구현했기 때문에, Dog 객체를 생성하여 사용할 수 있습니다.

4. 추상 클래스 정리

  • 추상 클래스는 공통적인 속성과 동작을 정의하는 틀(템플릿) 역할을 합니다.
  • 객체를 생성할 때 필요한 모든 동작을 정의하지 않기 때문에, 객체를 직접 생성할 수 없습니다.

추상 클래스의 강제성과 부모 클래스와의 차이

추상 클래스와 부모 클래스는 둘 다 상속을 통해 자식 클래스에 멤버(필드, 메서드)를 전달합니다.
하지만 추상 클래스강제성을 추가적으로 제공하며, 이는 두 개념의 주요 차이점 중 하나입니다.

추상 클래스의 강제성

  • 추상 클래스는 자식 클래스가 반드시 구현해야 할 추상 메서드를 정의할 수 있습니다.
  • 자식 클래스는 추상 메서드를 오버라이딩하지 않으면 컴파일 오류가 발생합니다.
  • 이를 통해 모든 자식 클래스가 일관된 인터페이스를 제공하도록 강제할 수 있습니다.

추상 클래스와 부모 클래스와의 차이

  • 추상 클래스
    • 추상 메서드를 포함할 수 있습니다(자식 클래스가 반드시 구현해야 합니다).
    • 객체를 직접 생성할 수 없습니다.
    • 자식 클래스에 특정 메서드 구현을 강제할 수 있습니다.
    • 공통 속성과 강제 구현을 통한 일관된 설계를 제공합니다.
    • 일관된 규칙과 동작 강제가 필요한 경우 사용합니다.
  • 부모 클래스
    • 추상 메서드를 포함할 수 없습니다.
    • 객체를 직접 생성할 수 있습니다.
    • 강제성이 없습니다.
    • 공통 동작을 정의하거나 기본 동작을 물려주는 역할을 합니다.
    • 동작 강제 없이 기본 구현만 제공할 때 사용합니다.

예제

  1. 추상 클래스
public abstract class Animal {
	public abstract void makeSound(); // 추상 메서드
}

public class Dog extends Animal {
	@Override
    public void makeSound() {
    	System.out.println("멍멍!");
    }
}
  1. 부모 클래스
  • 자식 클래스는 makeSound 메서드를 구현하지 않아도 됩니다.
public class Animal {
	public void makeSound() {
    	System.out.println("동물 사운드 출력");
    }
}

public class Dog extends Animal {
	// makeSound 구현 필요 없음 (선택 사항)
}

8.4 인터페이스

현실 세계에서 상속은 부모뿐만 아니라 조부모나 친척 등으로부터 받을 수 있습니다. 이처럼 만약 자바에서 자식 클래스가 여러 부모 클래스로부터 상속받을 수 있다면, 더 많은 동작을 수행할 수 있어 유용할 것처럼 보입니다.
하지만 자바에서는 다중 상속허용하지 않습니다.

자바에서 다중 상속을 허용하지 않는 이유

1. 충돌 문제

  • 다중 상속에서는 여러 부모 클래스에서 동일한 이름의 메서드가 정의될 경우, 어떤 메서드를 호출해야 할지 알 수 없습니다.

2. 복잡성 증가

  • 다중 상속은 클래스 간의 관계를 복잡하게 만들고, 코드의 가독성과 유지보수성을 저하시킬 수 있습니다.

8.4.1 인터페이스란

자바에서는 다중 상속을 지원하지 않는 대신 유사한 기능을 제공하는 인터페이스를 도입했습니다.
인터페이스는 클래스에서 구현할 메서드들이 선언된 집합입니다. 인터페이스에 선언된 메서드의 동작 구현은 각 클래스에서 담당합니다. 클래스는 여러 인터페이스를 구현할 수 있으므로, 다중 상속과 비슷한 효과를 낼 수 있습니다.

인터페이스의 특징

1. 추상적인 설계도

  • 인터페이스는 메서드의 시그니처만 선언하며, 구현은 각 클래스에서 담당합니다.

2. 다중 구현 가능

  • 클래스는 여러 인터페이스를 구현할 수 있습니다. 이를 통해 다중 상속과 비슷한 효과를 얻을 수 있습니다.

3. implements 키워드

  • 클래스가 인터페이스를 구현할 때는 implements 키워드를 사용합니다.

4. 구성 요소

  • 메서드: 인터페이스에 선언된 모든 메서드는 기본적으로 public abstract입니다. abstract를 붙이지 않아도 자동으로 추상 메서드로 인식됩니다.
  • 필드: 인터페이스의 필드는 public static final로 고정됩니다. 인터페이스는 상수만 포함 가능하며 해당 상수에 접근할 때는 인터페이스명을 사용합니다.

5. 자바 8 이후 추가 기능

  • default 메서드: 기본 구현을 가진 메서드를 선언할 수 있습니다. 디폴트 메서드는 메서드 동작이 구현된 메서드입니다. 디폴트 메서드는 인터페이스를 구현한 클래스에서 오버라이딩할 수 있습니다.
  • static 메서드: 인터페이스 자체에서 호출 가능한 정적 메서드를 선언할 수 있습니다. 인터페이스 이름으로 직접 호출할 수 있으며, 디폴트 메서드처럼 동작이 구현되어 있지만, 구현 클래스에서 오버라이딩할 수 없습니다.

인터페이스 형식

[접근 제한자] interface 인터페이스명 {
	자료형 변수명 =; // 상수
    반환형 메서드명(매개변수1, 매개변수2, ...) {}; // 추상 메서드
    default 반환형 메서드명(매개변수1, 매개변수2, ...) {}; // 디폴트 메서드
}

8.4.2 실습: 인터페이스 구현하기

인터페이스 정의 및 구현

인터페이스 정의

public interface Animal {
	void makeSound(); // 추상 메서드
}

인터페이스 구현

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

public class Cat implements Animal {
	@Override
    public void makeSount() {
    	System.out.println("야옹~");
    }
}

인터페이스 사용

public class Main {
	public static void main(String[] args) {
    	Animal dog = new Dog();
        Animal cat = new Cat();
        
        dog.makeSound(); // 출력: 멍멍!
        cat.makeSound(); // 출력: 야옹~
    }
}

다중 구현

인터페이스 정의

public interface Vehicle {
	void start();
    void stop();
}

public interface Electric {
	void chargeBattery();
}

인터페이스 구현

public class ElectricCar implements Vehicle, Electric {
	@Override
    public void start() {
    	System.out.println("시동 켜짐.");
    }
    
    @Override
    public void stop() {
    	System.out.println("시동 꺼짐.");
    }
    
    @Override
    public void chargeBattery() {
    	System.out.println("배터리 충전 중...");
    }
}

인터페이스 사용

public static void main(String[] args) {
	ElectricCar car = new ElectricCar();
    car.start();			// 출력: 시동 켜짐.
    car.chargeBattery();	// 출력: 배터리 충전 중...
    car.stop(); 			// 출력: 시동 꺼짐.
}

8.4.3 인터페이스에서 상수만 사용 가능한 이유와 기본 접근 제한자가 고정된 이유

인터페이스에서 상수만 사용 가능한 이유

1. 인터페이스의 설계 철학

  • 인터페이스는 행동(메서드)의 규약을 정의하기 위해 설계되었습니다.
  • 인터페이스는 객체의 설계도 역할을 하므로, 상태(필드 값)를 가지지 않고, 행동(메서드)만 선언하는 것이 목적입니다.
  • 따라서, 인터페이스에 선언된 변수는 상수(변하지 않는 값)로 제한됩니다.

2. 다중 구현과 데이터 충돌 방지

  • 클래스가 여러 인터페이스를 구현하는 경우, 만약 인터페이스에서 일반 필드를 허용한다면, 서로 다른 인터페이스에서 같은 이름의 필드가 정의될 경우 충돌이 발생할 수 있습니다.
  • 이를 방지하기 위해 인터페이스는 필드를 상수로만 허용하여 불변성을 보장합니다.

3. 상수로의 고정

  • 인터페이스의 필드는 public static fianl로 고정됩니다.
    • public: 어디서든 접근이 가능합니다.
    • static: 인터페이스 자체와 연결되어 있으며, 객체 생성 없이 사용이 가능합니다.
    • final: 한 번 값이 설정되면 변경할 수 없습니다.

인터페이스에서 기본 접근 제한자가 고정된 이유

인터페이스의 모든 멤버(메서드와 필드)는 자바 명세에 따라 기본 접근 제한자가 고정되어 있습니다. 즉, 접근 제한자를 명시하지 않아도 컴파일러가 자동으로 처리해줍니다.

1. 메서드

  • 추상 메서드
    • 기본적으로 public abstract로 지정됩니다.
    • 모든 클래스에서 해당 메서드를 구현할 수 있도록 공개(public)되며, 구현이 없으므로 추상적(abstract)입니다.
    public interface Animal {
    	void makeSound(); // 컴파일러가 public abstract를 자동으로 추가합니다.
    }
  • default 메서드(자바 8 이후)
    • 인터페이스에서 기본 동작을 제공하기 위해 선언되며, 기본적으로 public 입니다.
    default void run() {
    	System.out.println("Animal is running.");
    }
  • static 메서드
    • 인터페이스 자체에서 호출할 수 있는 정적 메서드로, public으로 고정됩니다.
    static void info() {
    	System.out.println("Static method in interface");
    }

2. 필드

  • 모든 필드는 public static final로 고정됩니다.
  • 필드는 상수로만 사용되며, 명시적으로 접근 제한자를 선언하지 않아도 컴파일러가 자동으로 추가합니다.

예제

public interface Constants {
	int MAX = 100; // 컴파일러가 자동으로 public static final을 추가합니다.
}

컴파일 후

public interface Constants {
	public static final int MAX = 100;
}
profile
donggyun_ee

0개의 댓글