Java Study #12 - 추상 클래스

allzeroyou·2022년 2월 23일
0

Java

목록 보기
12/14

추상 클래스(Abstract class)

메서드의 본체가 완성되지 않은 미완성 메서드

자바는 크게 클래스, 인터페이스로 나뉜다.
마치 세상은 크게 여자, 남자/ 생물, 무생물로 나뉘듯 말이다.
이때 클래스는 일반클래스, 추상 클래스로 또 나뉘는데
이때 일반 클래스는 붕어빵 틀에 비유할 수 있으며 붕어빵을 만들려고 붕어빵 틀을 제작하듯, 클래스의 존재 목적은 객체를 생성함에 있다.
또한 추상 클래스는 추상 메서드가 1개라도 있으면 추상 클래스이며, 이는 객체를 생성할 수 없다.

인터페이스는 인터페이스가 되는 조건이 있는데, 첫번째는 100% 추상 메서드이여되며 두번째는 100% static&final 필드 이어야 되며 세번째는 접근지정자는 public이어야 된다.
추상 클래스로 만들어진 인터페이스는 추상 클래스와 마찬가지로 객체를 생성할 수 없다.

추상 클래스의 특징

내부의 미완성메서드(추상메서드)가 있어서 객체를 생성할 수 없다.

변수 a를 선언해보자.
지역변수 a는 stack 메모리에 저장.
클래스 A(객체)안 필드 a는 Heap 메모리에 저장된다.

이때 Heap 메모리는 미완성인 상태로 저장이 안되기에, 항상 강제초기화가 된다.
그래서

class A{
	int a; 
}

처럼 필드 값에 아무런 값을 넣지 않게되면 숫자는 0, 나머지는 null, boolean은 false 등으로 각각 강제초기화 값으로 초기화가 된다.
이 상태로 System.out.println(A.a)을 할 경우 0이 출력된다.

반면에, stack 메모리의 경우

	int a; 

로 아무런 값을 넣지 않은 경우, 아무런 값이 들어가지 않은 상태로 저장이 된다.
이 상태로 System.out.println(a)을 할 경우 오류가 발생한다.

즉, Heap 메모리에 들어가는 녀석은(객체 생성을 위해) 완성된 형태(값이 채워진 상태)로 들어갈 수 있다는 것이다!

힙 메모리에는 값이 빈 필드가 저장될 수 없으므로 초기화하지 않은 필드를 힙 메모리에 저장하려고 하면 강제로 값을 초기화한다. 하물며, 미완성 메서드는 당연히 힙 메모리에 포함될 수 없는 것이다.
마치 단팥 앙꼬가 완성되지 않은 상태에서 붕어빵을 찍어낼 수 없는 것과 같은 원리(붕어빵 생성 x = 객체 생성x)

그렇다면, 붕어빵을 찍어내지 못하는 붕어빵 틀을 왜 만들까?

일반 클래스를 붕어빵 틀, 객체를 붕어빵이라고 비유할 수 있는데 이를 맞춰 추상 클래스는 붕어빵 틀을 만드는데 필요한 붕어빵 틀의 부품이라고 할 수 있다.
이 개념을 클래스로 이제 도입해보자.
추상 클래스는 직접 객체를 생성할 수 없지만 이 추상 클래스를 상속한 자식 클래스를 생성하면 그 자식 클래스는 객체를 생성할 수 있다. 이때 생성된 객체에는 부모 클래스의 추상 메서드가 구현돼 있다.

추상 클래스를 상속하는 자식 클래스는 반드시 부모에게 상속받은 미완성 메서드(추상 메서드)를 반드시 완성(오버라이딩)해야 한다.

오버라이딩 vs 구현하기(implements)

오버라이딩은 부모클래스의 메서드(완성/미완성)를 자식클래스에서 재정의(완성)한다.

구현하기는 부모클래스의 미완성메서드(추상 클래스)를 자식 클래스에서 재정의(완성)한다.

이때, 메서드 완성/미완성의 구분은 어떻게 할까?

중괄호 안에 내용이 아닌, 바로 중괄호({})의 여부이다.

추상 클래스 타입의 객체 생성 방법

추상 클래스 자체로는 직접 객체를 생성할 수 없지만, 자식 클래스를 생성해 객체를 생성하고 부모 클래스인 추상 클래스 타입으로 선언할 수 있다.
이렇게 추상 클래스의 객체 생성하는 방법은 자식 클래스의 생성 여부에 따라 크게 2가지로 나뉜다.

1. 추상 클래스를 일반 클래스로 상속해 객체 생성

추상 클래스를 상속한 자식 클래스가 일반 클래스로 정의되기 위해서는 반드시 상속받은 추상 메서드를 구현해야 한다. 자식 클래스는 일반 클래스이므로 객체를 생성할 수 있고, 이렇게 생성한 객체는 다형적 표현으로 부모 추상 클래스 타입으로 선언할 수 있다.

자식 클래스 B가 일반 클래스로 정의되기 위해서는 반드시 상속받은 추상메서드 abc()를 구현해야 한다.
클래스 B는 일반 클래스이므로 객체를 생성할 수 있고 이렇게 생성한 객체는 다형적 표현으로 부모 추상 클래스 타입으로 선언할 수 있다.

2. 익명 이너 클래스 사용

이는 컴파일러가 내부적으로 추상 클래스를 상속해 메서드 오버라이딩을 수행한 클래스를 생성하고, 그 클래스로 객체를 생성하는 방법이다. 이때 내부적으로 생성된 클래스명은 전혀 알 수 없으므로 개발자의 입장에서는 익명(이름이 없는) 클래스가 되는 것이다.

익명 이너 클래스의 문법 구조

클래스명 참조 변수명 = new 생성자(){
	// 추상 클래ㅅ에 포함된 추상 메서드 오버라이딩
}


A()는 클래스 A의 생성자를 호출하는 것이 아니라 컴파일러가 클래스 A를 상속받아 abc()메서드를 오버라이딩한 익명 클래스의 생성자를 호출하는 것을 의미한다.

2가지 방법 중 어떤 방법을 선택해 사용해야 할까?
1번째 방법은 추상클래스를 일반클래스로 상속해 객체를 생성하기 때문에 개발자가 직접 class를 만들어야 하는 번거로움이 있지만, 여러개의 객체를 사용할때와 클래스를 직접 지정할 때 유용하다.
2번째 방법은 클래스를 컴파일러가 만들어줘 직접 만들지 않아도 되지만, 클래스 이름은 없으며 객체 1개를 만들때 유용하다.

인터페이스

객체지향 프로그래밍 요소로 일상생활에서 흔히 쓰이는 인터페이스의 의미를 생각해보자.
냉장고가 콘센트가 호환이 잘 된다면, 인터페이스가 일치하는 것이고 커피포트와 콘센트가 호환이 잘 안된다면 인터페이스가 일치하지 않는 것이다.
이처럼 인터페이스는 입출력 방식의 호환성을 의미하는데, 자바에서는 어떤 의미일까?

인터페이스 정의와 특징

인터페이스는 3가지 특징이 있는데,
1. 모든 필드가 public static final로 정의

  • 접근지정자: public
  • 제어자: static은 객체 생성을 하지 않고도 바로 사용 o
  • 키워드: final 키워드로 값이 입력된 후 값 변경 x
  1. static과 default 메서드이외의 모든 메서드는 public abstract으로 정의
    • abstract메서드(추상 메서드, 미완성 메서드)
  2. 자체 객체 생성 x

인터페이스 내 필드와 메서드에 사용할 수 있는 제어자(modifier)가 확정돼 있어 필드와 메서드 앞에 제어자를 생략해도 컴파일러가 자동으로 추가.

인터페이스 B의 경우 제어자를 명시적으로 적어주지 않았지만, 인터페이스이기에 컴파일러가 각각의 제어자를 자동으로 추가해줌을 알 수 있다.
또한 final 필드임을 확인하기 위해 main메서드에서 값을 변경하려고 했으나 오류가 발생해 한번 값을 입력한 후에 변경할 수 없는 final 필드임을 알 수 있다.

인터페이스의 상속

클래스-클래스 상속에 extends를 사용해 상속했다면, 인터페이스-클래스 상속에는 implements를 사용한다.
인터페이스의 상속에 있어 큰 장점은 다중 상속이 가능하다는 점이다.
1개의 클래스가 여러개의 인터페이스를 상속할 때는 쉼표로 구분해 나열.

클래스에서 불가했던 다중 상속이 어떻게 인터페이스에서는 가능할까?
클래스에서 다중 상속이 불가했던 이유를 떠올려보자.
두 부모 클래스에 동일한 이름의 필드/메서드가 존재할 때 이를 내려받으면 ambigous error(충돌)이 발생하기 때문이다.
하지만 인터페이스에서는 충돌이 일어날 수 없다.
모든 필드가 public static final로 정의되어 있기 때문에 실제 데이터 값은 각각의 인터페이스 내부에 존재(저장 공간이 분리되어 저장)하기 때문에 오류가 발생하지 않기 때문에.
또한 메서드도 모두 미완성(abstract메서드)이라 어차피 자식 클래스 내부에서 완성(implements)해 사용하기 때문이다!


인터페이스 A를 선언해주었고 인터페이스 B는 bcd()메서드를 선언했지만 앞 제어자를 생략했다. 그러나 인터페이스는 컴파일러가 자동으로 public abstract을 추가해줌을 알 수 있다.
또한 클래스 C는 A를 상속했는데, 클래스-인터페이스 상속에 implements 키워드를 사용해 추상 메서드(미완성 메서드)를 {}를 넣어 완성해주었다.
클래스 D는 클래스 B를 상속했는데, bcd()메서드는 {}를 넣어 완성시켰으나 메서드 앞 접근지정자를 생략해 public이 아닌 default 접근지정자가 지정되었다. 상속시, 접근지정자의 범위는 같거나 넓어야 하는 것이 원칙이나 클래스 D는 접근지정자를 생략해 default로 지정되었기에 오류가 발생.

인터페이스의 객체 생성 방법

인터페이스도 추상메서드를 포함하고 있기 때문에(붕어빵 틀 부품) 객체를 직접 생성 불가
1. 인터페이스를 일반 클래스로 상속해 객체 생성

인터페이스 A를 클래스 B가 implements를 통해 상속했고 main 메서드에서 객체를 생성하고 메서드를 호출한 결과, 클래스 B의 메서드가 출력됨.

  1. 익명이너클래스 사용

인터페이스 A를 main 메서드 안에서 A라는 객체를 생성하고 참조변수 a1에 저장하면 오류가 발생한다.
인터페이스는 직접 객체를 생성할 수 없기 때문이다. 따라서 뒤에 중괄호 안에 이름을 모르는 클래스를 생성시켜 그 속에 완성된 메서드를 넣으면 오류가 발생하지 않는다. 이를 익명이너클래스라고 한다.
이 방법을 이용해, 2개의 객체를 생성하고 메서드를 호출.

인터페이스의 필요성

인터페이스는 왜 사용해야 할까?
사실 이 주제는 가장 중요한 부분인데, 어플리케이션을 만든다고 생각해보자.
이때 운영체제, 회사 별 그래픽 카드 드라이버가 다를 것이다. 이를 고려해 각각 다르게 어플리케이션 코드를 구현하려면 꽤나 복잡하고 어렵다.
따라서 어느 회사 제품이든 하나의 인터페이스를 만들어, 이 인터페이스 코드 대로 제품을 만든다면 단 하나의 어플리케이션으로도 제작할 수 있을 것이다.
인터페이스는 다형성을 할 수 있는 이유이기도 하다.

디폴트 메서드와 정적 메서드

인터페이스 내에 완성메서드인 default 메서드를 포함할 수 있다.

인터페이스 A는 메서드가 2개 있는데, abc메서드는 2021년 생성했고 bcd는 2022년 새롭게 추가된 메서드이다. 이때 a를 상속한 클래스 B는 abc메서드를 완성하였다.
클래스 c는 a를 상속했는데, abc메서드를 완성했고 bcd 메서드를 일반 메서드처럼 오버라이딩했다.

메인메서드에서 2개의 객체를 생성한 후 메서드를 호출한 결과를 유심히 보자.
A 타입이지만 실제 객체를 b로 만드는 과정에서 인터페이스 A를 상속한 B 클래스의 abc로 오버라이딩되었고.
bcd 메서드를 호출하면, 클래스b에서 오버라이딩하지 않았기에 인터페이스 a의 bcd가 호출된다.

다음, a타입이지만 실제 객체를 만드는 과정에서 인터페이스 a를 상속한 c 클래스의 abc로 오버라이딩되었고,
bcd 메서드를 호출하면, 클래스 c에서 오버라이딩되어 클래스 c의 bcd가 호출된다.

자식 클래스에서 부모 인터페이스의 디폴트 메서드 호출방법

인터페이스안 디폴트 메서드는 일반 메서드처럼 행동하기에, 자식클래스에서 부모 인터페이스 내부의 디폴트 메서드를 호출할 수 있다.
부모 인터페이스의 디폴트 메서드를 어떻게 호출할까?

인터페이스 A의 디폴트 메서드 abc이다. 이는 완성메서드이다.
클래스 B는 a를 상속했고, abc를 오버라이딩하였다. 이때 부모인터페이스의 default메서드를 호출하고하는 방법은

부모인터페이스명.super.메서드명

으로 작성한다.

그렇게 되면, 메인 메서드에서 객체를 생성한후 메서드를 호출한 결과를 확인 해보자.
클래스 B의 abc를 호출하려고 갔으나 A를 상속하고 default 메서드를 호출하고 있어 a 인터페이스로 가서 abc메서드를 호출한 후에 b 클래스로 돌아와 abc 메서드를 호출한다.

자식 클래스에서 부모 인터페이스의 정적 메서드 호출방법

인터페이스는 static 메서드도 포함할 수 있는데, 클래스 내부의 정적 메서드와 동일한 기능을 하며 객체를 생성하지 않고 다음의 호출법으로 바로 호출할 수 있다.

인터페이스명.정적메서드명


인터페이스 a를 생성하고 그 안에 static메서드 abc를 생성하자.
그 후 메인메서드에서 정적메서드를 호출하기 위해, 인터페이스명.정적메서드명인 A.abc()로 호출한 결과 a 인터페이스의 정적메서드가 잘 호출됨을 확인할 수 있다.

profile
모든 건 zero 부터, 차근차근 헛둘헛둘

0개의 댓글