JAVA - 추상화

흑이·2022년 5월 19일
0

추상화

자바에서의 추상화는 객체의 공통적인 속성기능추출하여 정의하는 것을 의미합니다.
즉, 공통적인 속성과 기능을 정의하고 하위 클래스들을 생성할 수도 있고, 반대로 하위 클래스들의 공통성을 모아 상위 클래스를 정의할 수 있다.



abstract 제어자

영단어 abstract의 사전적 의미는 ‘추상적인'이라는 뜻을 가지고 있는데, 자바의 맥락에서 abstract라는 단어가 내포하는 의미는 ‘미완성'이라 정리할 수 있다.

abstract class AbstractExample { // 추상 메서드가 최소 하나 이상 포함돼있는 추상 클래스
	abstract void start(); // 메서드 바디가 없는 추상메서드
}

추상 메서드는 메서드의 시그니처만 있고 바디가 없는 메서드를 의미하는데, abstract 키워드를 메서드 이름 앞에 붙여주어 해당 메서드가 추상 메서드임을 명시한다.

추상 메서드는 충분히 구체화되지 않은 ‘미완성 메서드’이며, 미완성 메서드를 포함하는 클래스는 ‘미완성 클래스'를 의미하는 추상 클래스가 된다.

AbstractExample abstractExample = new AbstractExample(); // 에러발생. 

추상 클래스는 미완성 설계도이기 때문에 메서드 바디가 완성이 되기 전까지 이를 기반으로 객체 생성이 불가하다.



왜 이렇게 객체도 생성하지 못하는 미완성 클래스를 만드는 걸까?


추상 클래스

상속 관계에 있어 새로운 클래스를 작성하는데 매우 유용하다.

메서드의 내용이 상속을 받는 클래스에 따라서 종종 달라지기 때문에 상위 클래스에서는 선언부만을 작성하고

실제 구체적인 내용은 상속을 받는 하위 클래스에서 구현하도록 비워둔다면 설계하는 상황이 변하더라도 보다 유연하게 대응할 수 있다.

오버라이딩을 통해 추상 클래스로부터 상속받은 추상 메서드의 내용을 구현하여 메서드를 완성시킬 수 있고, 이렇게 완성된 클래스를 기반으로 해당 객체를 생성할 수 있다.


예제

abstract class Animal {
	public String kind;
	public abstract void sound();
}

class Dog extends Animal { // Animal 클래스로부터 상속
	public Dog() {
		this.kind = "포유류";
	}

	public void sound() { // 메서드 오버라이딩 -> 구현부 완성
		System.out.println("멍멍");
	}
}

class Cat extends Animal { // Animal 클래스로부터 상속
	public Cat() {
		this.kind = "포유류";
	}

	public void sound() { // 메서드 오버라이딩 -> 구현부 완성
		System.out.println("야옹");
	}
}

class DogExample {       
    public static void main(String[] args) throws Exception {
       Animal dog = new Dog();
       dog.sound();

       Cat cat = new Cat();
       cat.sound();
    }
 }

//Output
멍멍
야옹

Animal 클래스 안에 abstract 키워드를 사용한 sound() 메서드가 추상 메서드로 선언되었고, 따라서 이를 포함하는 Animal 클래스 또한 abstract 키워드를 사용하여 추상 클래스로 만들어주었습니다.

그 이후 추상 클래스 Animal를 상속받은 Dog 클래스와 Cat 클래스 안에 추상 메서드 sound()를 각각 오버라이딩하여 각 객체에 맞는 구현부를 완성해주었고

마지막으로 이렇게 완성된 클래스를 기반으로 dog 인스턴스와 cat 인스턴스를 생성하여 sound() 메서드를 호출했습니다.

그 결과 출력값으로 각각 “멍멍"과 “야옹"이라는 값이 반환되었습니다.

이렇듯 추상 클래스를 사용하면 상속을 받는 하위 클래스에서 오버라이딩을 통해 각각 상황에 맞는 메서드 구현이 가능하다는 장점이 있다.

만약 여러 사람이 함께 개발하는 경우, 공통된 속성과 기능임에도 불구하고 각각 다른 변수와 메서드로 정의되는 경우 발생할 수 있는 오류를 미연에 방지할 수 있습니다.



final 키워드

final 키워드는 필드, 지역 변수, 클래스 앞에 위치할 수 있으며 그 위치에 따라 그 의미가 조금씩 달라지게 됩니다.

각각 조금의 차이점이 있지만 결국 공통적으로 변경이 불가능하고 확장할 수 없다는 점에서 유사하다.

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

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

각각의 클래스, 메서드, 그리고 변수 앞에 final 제어자가 추가되면 이제 해당 대상은 더이상 변경이 불가하거나 확장되지 않는 성질을 지니게 된다.



인터페이스

인터페이스의 사전적 의미는 “서로 다른 두 시스템, 장치, 소프트웨어 따위를 서로 이어 주는 부분 또는 그런 접속 장치를 의미”합니다.

즉 어떤 두 대상 간의 연결 내지 소통을 돕는 중간 가교 역할을 수행하는 것인데, 자바에서 인터페이스도 이와 유사한 기능을 가지고 있다고 할 수 있습니다.

인터페이스는 추상클래스보다 더 높은 추상성을 가지고 있습니다. 추상 클래스가 구현부가 미완성된 추상 메서드와 멤버 변수를 포함 할 수 있는 반면에 인터페이스는 오직 추상 메서드상수만을 멤버로 가질 수 있고 다른 어떤 요소 포함될 수 없습니다.

인터페이스를 작성하는 것은 기본적으로 클래스를 작성하는 것과 유사하지만, class 키워드 대신 interface 키워드를 사용한다는 점에서 차이가 있습니다. 또한 일반 클래스와 다르게, 내부의 모든 필드가 public static final로 정의되고

staticdefault 메서드 이외의 모든 메서드가 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 키워드를 사용

class ExampleClass implements ExampleInterface { ... }

클래스 간의 상속은 단 하나만 가능

하위 클래스는 단 하나의 상위 클래스만 상속받을 수 있다.

그러나 인터페이스는 다중적 구현이 가능하다.
하나의 클래스가 여러 개의 인터페이스를 구현할 수 있습니다.

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


interface Animal {public abstract void cry();} // 인터페이스 선언. public abstract 생략 가능.
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();
    }
}

//Output
멍멍!
원반 던지기
야옹~!
쥐 잡기

DogCat 클래스는 각각 AnimalPet 인터페이스를 다중으로 구현하여 동시에 각각의 객체에 맞는 메서드를 오버라이딩하고, 그 내용을 출력값으로 돌려주고 있다.


클래스는 다중 상속이 불가능한데, 왜 인터페이스는 여러 개를 구현해도 될까?

클래스에서 다중 상속이 불가능했었던 이유는 만약 부모 클래스에 동일한 이름의 필드 또는 메서드가 존재하는 경우 충돌이 발생하기 때문인데

인터페이스는 애초에 미완성된 멤버를 가지고 있기 때문에 충돌이 발생할 여지가 있을 수 없고 따라서 다중적 구현이 가능해진다.

나아가, 다음과 같이 클래스를 상속받으면서 동시에 인터페이스를 구현하는 것도 가능합니다.

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();
    }
}

//Output
멍멍!
원반 던지기
야옹~!
쥐 잡기

DogCat 클래스가 Animal 클래스를 상속 받도록 하였고, Pet 인터페이스를 구현하도록 하여 같은 결과물을 출력



0개의 댓글