티스토리 뷰

728x90
반응형

final 키워드

 

영어로 '최종의', '마지막의'라는 뜻을 지니고 있는 final 키워드는 필드, 메서드, 클래스 앞에 위치할 수 있으며

 

각각 위치에 따라 의미가 조금씩 다르다.

 

조금씩 다른 의미를 지녔지만 공통적으로 변경이 불가능하고 확장할 수 없다는 특징을 가지고 있음을 알 수 있다.

 

이를 코드로 간략히 표현해보면 다음과 같다.

final class FinalEx { // 확장/상속 불가능한 클래스
	final int x = 1; // 변경되지 않는 상수

	final void getNum() { // 오버라이딩 불가한 메서드
		final int localVar = x; // 상수
		return x;
	}
}

정리하면 final 제어자가 앞에 붙으면 해당 대상은 더 이상 변경이나 확장이 불가능한 성질을 지니게 된다.

 


인터페이스(Interface)

 

인터페이스는 앞서 알아본 추상 클래스와 함께 자바 객체지향 프로그래밍 추상화에서 핵심을 담당한다.

 

직역하면 두 개의 대상을 연결한다는 의미를 가진 인터페이스는

 

"서로 다른 두 시스템 장치, 소프트웨어 따위를 서로 이어주는 부분 혹은 접속장치"

 

라 정의할 수 있다.

 

추상화의 핵심을 담당하는 다른 축인 추상 클래스와 비교해서 이해할 수도 있는데,

 

인터페이스가 추상 클래스에 비해 더 높은 추상성을 가지고 있다고 보면 된다.

 

구체적으로 비교하자면 추상 클래스엔 추상 메서드가 하나 이상만 포함되면 되지만

 

인터페이스는 오로지 추상 메서드와 상수만을 멤버로 가질 수 있다.

 

인터페이스를 작성하는 것은 클래스와 유사하지만 class 키워드가 아닌 interface 키워드를 이용해야 한다.

 

또한 일반 클래스와 다르게 모든 필드가 public static final로 정의되고(생략 가능),

 

모든 메서드가 public abstract로 정의된다(생략 가능).

public interface InterfaceEx {
    public static final int rock =  1; // 인터페이스 인스턴스 변수 정의
    final int scissors = 2; // public static 생략
    static int paper = 3; // public & final 생략

    public abstract String getPlayingNum();
		void call() //public abstract 생략 
}

생략된 부분은 컴파일러가 자동으로 추가해 준다.

 

추상 클래스와 마찬가지로 인터페이스는 그 자체로는 객체를 생성할 수가 없다.

 

따라서 메서드 바디 정의를 위한 클래스를 따로 작성해야 하는데,

 

이 경우에 쓰이는 키워드가 implements이다.

 

지난 번 글에 나온 상속에서 사용되는 키워드 extends와 비슷하게 사용된다고 보면 된다.

class 클래스명 implements 인터페이스명 {
		... // 인터페이스에 정의된 모든 추상메서드 구현
}

차이가 있다면 인터페이스를 구현한 클래스는

 

해당 인터페이스에 존재하는 모든 추상메서드를 구현해야 한다.

 

다르게 말하면 어떤 클래스가 인터페이스를 구현한다는 것은

 

그 클래스에게 인터페이스의 추상 메서드를 반드시 구현하도록 강제한다는 것이다.

 

또는 인터페이스가 가진 모든 추상 메서드를 해당 클래스 내에서 오버라이딩 하여 바디를 완성한다고 할 수도 있겠다.

 

상속과의 또 다른 차이는 다중 구현인데,

 

하나의 클래스는 두 개 이상의 인터페이스를 구현할 수 있다.

class ExampleClass implements ExampleInterface1, ExampleInterface2, ExampleInterface3 { 
				... 생략 ...
}

계속해서 예제를 보자.

interface Animal { // 인터페이스 선언. public abstract 생략 가능.
	public abstract void cry();
} 

interface Pet {
	void play();
}

class Dog implements Animal, Pet { // Animal과 Pet 인터페이스 다중 구현
    public void cry(){ // 메서드 오버라이딩
        System.out.println("멍멍!"); 
    }

    public void play(){ // 메서드 오버라이딩
        System.out.println("원반 던지기");
    }
}

class Cat implements Animal, Pet { // Animal과 Pet 인터페이스 다중 구현
    public void cry(){
        System.out.println("야옹~!");
    }

    public void play(){
        System.out.println("쥐 잡기");
    }
}

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

        dog.cry();
        dog.play();
        cat.cry();
        cat.play();
    }
}

// 출력 결과
멍멍!
원반 던지기
야옹~!
쥐 잡기

Dog와 Cat 클래스가 Animal, Pet 인터페이스를 다중구현해

 

각각 알맞은 메서드로 완성한 것을 확인할 수 있다.

 

또한 특정 클래스는, 다른 클래스로부터 상속을 받는 동시에

 

인터페이스를 구현할 수도 있다.

abstract class Animal { // 추상 클래스
	public abstract void cry();
} 
interface Pet { // 인터페이스
	public abstract void play();
}

class Dog extends Animal implements Pet { // Animal 클래스 상속 & Pet 인터페이스 구현
    public void cry(){
        System.out.println("멍멍!");
    }

    public void play(){
        System.out.println("원반 던지기");
    }
}

class Cat extends Animal implements Pet { // Animal 클래스 상속 & Pet 인터페이스 구현
    public void cry(){
        System.out.println("야옹~!");
    }

    public void play(){
        System.out.println("쥐 잡기");
    }
}

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

        dog.cry();
        dog.play();
        cat.cry();
        cat.play();
    }
}

// 출력 결과
멍멍!
원반 던지기
야옹~!
쥐 잡기

다중구현의 첫 예제에서 Animal 인터페이스를 클래스로 교체한 뒤

 

상속과 구현을 동시에 하는 예제이다. 출력 결과는 같다.

 


인터페이스의 장점

 

이번에는 실제로 인터페이스가 어떤 장점을 가지고 있는지 살펴보자.

 

지금부터 볼 코드엔 Provider 클래스에 의존하고 있는 User 클래스가 있다.

 

의존이라 함은 User 클래스에서 Provider 클래스의 특정 속성이나 기능을 가져와 사용하고 있다는 것이다.

public class InterfaceExample {
    public static void main(String[] args) {
        User user = new User(); // User 클래스 객체 생성
        user.callProvider(new Provider()); // Provider 객체 생성 후에 매개변수로 전달
    }
}

class User { // User 클래스
    public void callProvider(Provider provider) { // Provider 객체를 매개변수로 받는 callProvider 메서드
        provider.call();
    }
}

class Provider { //Provider 클래스
    public void call() {
        System.out.println("무야호~");
    }
}

// 출력 결과
무야호~

User 클래스 안의 callProvider 메서드에서 Provider 타입의 매개변수가 호출되는 것을 볼 수 있다.

 

이 상태에서 Provider 클래스에 변경사항이 생겨 Provider2로 교체해야 한다고 하자.

 

가장 쉬운 방법은 아래와 같을 것이다.

public class InterfaceExample {
    public static void main(String[] args) {
        User user = new User(); // User 클래스 객체 생성
        user.callProvider(new Provider2()); // Provider객체 생성 후에 매개변수로 전달
    }
}

class User { // User 클래스
    public void callProvider(Provider2 provider) { // Provider 객체를 매개변수로 받는 callProvider 메서드
        provider.call();
    }
}

class Provider2 { //Provider 클래스
    public void call() {
        System.out.println("야호~");
    }
}

// 출력 결과
야호~

별 특별할 거 없이 Provider 클래스를 Provider2로 변경했다.

 

하지만 여기서 불편한 점이 있는데, User 클래스에 손을 대야 한다는 것이 그것이다.

 

이번엔 코드 수정이 짧았지만 User 클래스 안의 Provider 호출이 몇 백 줄이라면?

 

하나하나 수정하다가 에러가 나기 십상일 것이다.

 

여기서 인터페이스를 이용하면 역할과 구현을 분리시켜

 

복잡한 구현의 내용이나 변경과 상관없이 해당 기능을 사용할 수 있다.

 

이를 도식으로 나타내면 다음과 같은데,

 

User 클래스가 바로 Provider 클래스에 접근하는 게 아니라

 

중간에 있는 인터페이스를 이용해 접근하도록 한다는 뜻이다.

 

조금 복잡하지만 코드로 나타내면 다음과 같다.

interface Cover { // 인터페이스 정의
    public abstract void call();
}

public class Interface4 {
    public static void main(String[] args) {
        User user2 = new User();
//        Provider provider = new Provider();
//        user2.callProvider(new Provider());
        user2.callProvider(new Provider2());
    }
}

class User {
    public void callProvider(Cover cover) { // 매개변수의 다형성 활용
        cover.call();
    }
}

class Provider implements Cover {
    public void call() {
        System.out.println("무야호~");
    }
}

class Provider2 implements Cover {
    public void call() {
        System.out.println("야호~");
    }
}

//출력 결과
야호~

먼저 Cover라는 인터페이스를 작성하고 이를 통해 Provider, Provider2 클래스에 구현했다.

 

User 클래스에서는 매개변수의 다형성을 활용하여 인터페이스 자체를 매개변수로 받도록 정의했다.

 

그러고 나면 Provider의 내용 변경 또는 교체가 일어나더라도 User클래스에는 손을 대지 않아도 된다.

 

이 장점은 User 클래스의 길이가 길수록 빛을 발하는데,

 

앞서 말했던 에러 가능성이 획기적으로 줄어들기 때문이다.

 

정리하자면 인터페이스의 장점은 사용자의 입장에선 기능이 가지는 역할과 구현을 분리시킴으로써

 

코드 변경의 번거로움을 최소화 하며 손쉽게 해당 기능을 사용할 수 있게 만드는 데 있다.

 

또한 개발자의 입장에서도 비슷하게 선언과 구현을 분리시켜 개발기간을 단축할 수 있고,

 

한 클래스의 변경이 다른 클래스에 미치는 영향을 최소화 할 수 있다는 장점이 있다.

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/06   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
글 보관함