본문 바로가기

STUDY/국비과정

[JAVA 웹 개발 공부] 국비지원 19일차 - 동적 바인딩, 상속, 구성, 추상 클래스, 인터페이스, compare()와 compareTo(), 디폴트 메서드

동적 바인딩

 

1. 바인딩

메소드 호출을 실제 메소드의 몸체와 연결하는 것을 바인딩(binding)이라고 한다.

자바가상머신(JVM)은 실행 단계에서 변수가 참조하는 객체의 실제 타입을 보고 적절한 메소드를 호출하게 된다. 

이것을 동적 바인딩(dynamic binding)이라고 한다.

 

2. 동적 바인딩

오버라이드된 메소드 호출이 컴파일 시간이 아닌 실행 시간에 결정되는 메커니즘을 의미한다.

오버라이드된 메소드가 부모 클래스 참조를 통하여 호출되는 경우에 객체의 타입에 따라서 서로 다른 메소드가 호출되게 하는 메커니즘이다. 즉 객체의 실제 타입이 호출되는 메소드를 결정하는 것이다.

 

 

상속 vs 구성

 

상속과 아주 유사하지만 상속과는 미묘하게 다른 구성(composition)이라는 기법이 있다.

구성은 클래스가 다른 클래스의 인스턴스를 클래스의 필드로 가지는 디자인 기법이다.

반면에 상속은 한 객체가 클래스를 상속받아서 부모 객체의 속성과 동작을 획득할 수 있는 기법이다.

구성 및 상속은 모두 클래스를 연결하여 코드 재사용성을 제공한다.

 

상속을 통해 생성된 클래스와 객체는 밀접하게 결합되어 있다. (의존하고 있다)

상속 관계에서 부모 클래스를 변경하면 코드가 손상될 위험이 있다.

구성을 통해 생성된 클래스와 객체는 느슨하게 결합되어 코드를 손상시키지 않고 구성 요소를 더 쉽게 변경할 수 있다.

 

상속 구성
is-a 관계 "A는 B의 일종이다." has-a 관계 "~은 ~을 가지고 있다."
사람은 객체다 사람은 이름을 가지고 있다.
자동차는 탈것이다. 자동차는 엔진을 가지고 있다.
사자, 개, 고양이는 동물이다. 도서관은 책을 가지고 있다.
집은 건축물의 일종이다. 집은 욕실을 가지고 있다.

 

 

추상 클래스

 

1. abstract

abstract는 미완성의 의미를 가지고 있다. 메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않는 추상 메서드를 선언하는데 사용된다.

제어자 대상 의미
abstract 클래스 클래스 내에 추상 메서드가 선언되어 있음을 의미한다.
메서드 선언부만 작성하고 구현부는 작성하지 않은 추상 메서드임을 알린다.

추상 클래스는 아직 완성되지 않은 메서드가 존재하는 '미완성 설계도'이므로 인스턴스를 생성할 수 없다.

인스턴스를 생성하지 못하게 클래스 앞에 제어자 'abstract'를 붙여 놓는 경우도 있다.

abstract class AbstractTest { // 추상 클래스(추상 메서드를 포함한 클래스)
	abstract void move(); // 추상 메서드(구현부가 없는 메서드)
}

 

2. 추상 메소드

추상 메소드는 메소드 앞에 'abstract'을 써준다.

추상적인 클래스 안에서만 존재할 수 있으며, 헤더(정의)만 있고 바디없는 메소드이다.

추상메소드를 포함하는 추상클래스는 인스턴스화 할 수 없으며, 추상클래스가 아니면 추상 메소드를 가질 수 없다.

추상 클래스를 상속받는 클래스는 추상 클래스의 추상 메소드를 반드시 오버라이드 해줘야 한다.

 

3. 추상 클래스

추상 클래스(abstract class)는 완전하게 구현되어 있지 않은 메소드를 가지고 있는 클래스를 의미한다.

메소드가 미완성되어 있으므로 추상 클래스로는 객체를 생성할 수 없다. 추상 클래스는 주로 상속 계층에서 추상적인 개념을 나타내기 위한 용도로 사용된다.

 

4. 추상 클래스의 용도

보통은 추상메소드를 사용하기 위해 추상 클래스를 사용하며, 부모 클래스가 인스턴스화 되면 안될 때와 부모에서 정의할 수 없는 메소드(도형 넓이구하기, 동물 소리내기 등)가 있을 때 추상 클래스를 사용한다.

 

ex) Animal 추상 클래스 

abstract class Animal {
	abstract void speak();
}

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

class Cat extends Animal {
	@Override
    void speak() {
		System.out.println("야옹");
	}
}

public class DynamicCallTest {
	public static void main(String[] args) {
		Animal a1 = new Dog();
		Animal a2 = new Cat();
		
		a1.speak(); // 멍멍
		a2.speak(); // 야옹
	}
}

 

 

인터페이스

 

1. 인터페이스

인터페이스는 일종의 추상클래스이다. 인터페이스는 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 추상클래스와 달리 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없다. 

오직 추상메서드와 상수만을 멤버로 가질 수 있으며, 그 외의 다른 어떠한 요소도 허용하지 않는다.

추상 클래스를 부분적으로만 완성된 '미완성 설계도'라고 한다면, 인터페이스는 구현된 것은 아무 것도 없고 밑그림만 그려져 있는 '기본 설계도'라 할 수 있다.

인터페이스도 추상클래스처럼 완성되지 않은 불완전한 것이기 때문에 그 자체만으로 사용되기 보다는 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다.

interface 인터페이스이름 {

   public static final 타입 상수이름 = 값;

   public abstract 메서드이름 (매개변수목록);

}

 

일반적인 클래스의 멤버들과 달리 인터페이스 멤버들은 다음과 같은 제약사항이 있다.

*모든 멤버변수는 public static final 이어야 하며, 이를 생략할 수 있다.

*모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.

(단, static 메서드와 디폴트 메서드는 예외)

 

2. 인터페이스의 상속

인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와는 달리 다중상속, 즉 여러 개의 인터페이스로부터 상속을 받는 것이 가능하다.

인터페이스는 클래스와 달리 Object 클래스와 같은 최고 조상이 없다.

 

3. 인터페이스의 구현

인터페이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없으며, 추상클래스가 상속을 통해 추상메서드를 완성하는 것처럼, 인터페이스도 자신에 정의된 추상메서드의 몸통을 만들어주는 클래스를 작성해야 하는데, 그 방법은 추상클래스가 자신을 상속받는 클래스를 정의하는 것과 다르지 않다. 다만 클래스는 확장한다는 의미의 키워드 'extends'를 사용하지만 인터페이스는 구현한다는 의미의 키워드 'implements'를 사용할 뿐이다.

class 클래스이름 implements 인터페이스이름 {

// 인터페이스에 정의된 추상메서드를 구현해야 한다.

}

 

interface CanFly {
	abstract void fly(); // 추상메소드. abstract 생략가능.
}

class Bird {
	
}

// 동작(CanFly)할 수 있는 Bird(새)를 상속한 Eagle(독수리)클래스
class Eagle extends Bird implements CanFly { 
	@Override
	public void fly() {
		System.out.println("독수리가 납니다.");
	}
}

public class TestBirds {
	public static void main(String[] args) {
		Eagle e = new Eagle();
		e.fly(); // 독수리가 납니다.
        
		CanFly e2 = new Eagle();
		e2.fly(); // 독수리가 납니다.
	}
}

 

 

인터페이스 vs 추상 클래스

 

추상클래스는 인터페이스와 유사하다. 이들은 객체화할 수 없고 주로 구현이 안 된 메소드들로 이루어진다. 하지만 추상 클래스에서는 일반적인 필드도 선언할 수 있으며, 일반적인 메소드도 정의할 수 있다. 인터페이스에서 모든 메소드는 public, abstract가 된다. 자바에서는 하나의 클래스만 상속받을 수 있지만 여러 개 인터페이스를 동시에 구현할 수 있다.

 

1. 추상클래스

만약 관련된 클래스들 사이에서 코드를 공유하고 싶다면 추상 클래스를 사용하는 것이 좋다.

공통적인 필드나 메소드의 수가 많은 경우, 또는 public 이외의 접근 지정자를 사용해야 하는 경우에 추상 클래스를 사용한다.

정적이 아닌 필드나 상수가 아닌 필드를 선언하기를 원할 때 사용한다.

 

2. 인터페이스

관련 없는 클래스들이 동일한 동작을 구현하기를 원할 때 사용한다. 예를 들어서 Comparable과 Cloneable과 같은 인터페이스는 관련없는 클래스들이 구현한다.

특정한 자료형의 동작을 지정하고 싶지만 누가 구현하든지 신경쓸 필요가 없을 때 사용한다.

다중 상속이 필요할 때 사용한다.

 

 

 

compare()와 compareTo()

 

compare()와 compareTo()는 선언형태와 이름이 약간 다를 뿐 두 객체를 비교한다는 같은 기능을 목적으로 고안된 것이다.

compareTo()의 반환값은 int이지만 실제로는 비교하는 두 객체가 같으면 0, 비교하는 값보다 작으면 음수, 크면 양수를 반환하도록 구현해야 한다. 이와 마찬가지로 compare()도 객체를 비교해서 음수, 0, 양수 중의 하나를 반환하도록 구현해야 한다.

*compareTo() 메서드 : 클래스 내부에 정렬 기준을 정의해주는 것
*compare() 메서드 : 정렬하고 싶은 클래스 외부에 Comparator를 선언하여 compare() 메서드를 재정의하는 것

 

equals메서드는 모든 클래스가 가지고 있는 공통적인 메서드이지만, Comparator를 구현하는 클래스는 오버라이딩이 필요할 수도 있다는 것을 알리기 위해서 정의한 것일 뿐, 그냥 compare(Object o1, Object o2)만 구현하면 된다.

*Comparable : 기본 정렬기준을 구현하는데 사용

*Comparator : 기본 정렬기준 외에 다른 기준으로 정렬하고자할 때 사용

 

 

 

디폴트 메서드

 

디폴트 메서드(default method)는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.

디폴트 메서드는 앞에 키워드 default를 붙이며, 추상 메서드와 달리 일반 메서드처럼 몸통{}이 있어야 한다. 

디폴트 메서드 역시 접근 제어자가 public이며, 생략가능하다.

 

새로 추가된 디폴트 메서드가 기본의 메서드와 이름이 중복되어 충돌하는 경우가 발생한다. 

이 충돌을 해결하는 규칙은 다음과 같다.

*여러 인터페이스의 디폴트 메서드 간의 충돌

-인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.

*디폴트 메서드와 조상 클래스의 메서드 간의 충돌

-조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.