정의: 자식 클래스에서 반드시 오버라이딩해야만 사용할 수 있는 메서드
오버라이딩(Overriding): 상속 관계에 있는 부모 클래스에서 이미 정의된 메소드를 자식 클래스에서 같은 시그니쳐를 갖는 메서드로 다시 정의하는 것
abstract class 클래스이름 {
...
abstract 반환타입 메서드이름();
...
}
// abstract class
public abstract class Person {
private String name;
private String gender;
public Person(String nm, String gen){
this.name=nm;
this.gender=gen;
}
@Override
public String toString(){
return "Name="+this.name+"::Gender="+this.gender;
}
// abstract method
public abstract void work();
public void changeName(String newName) {
this.name = newName;
}
}
제어 추상화
for, while 문도 사실 반복하는 개념을 제어 추상화 한 것이다.데이터 추상화
대상을 간단한 개념으로 일반화 하는 과정
추상화를 하면 할 수록 객체의 디테일함이 사라지고 공통된 특징만 남게 된다.
abstract class 전자제품 {
전원기능();
}
abstract class 통신기기 extends 전자제품 {
통화기능();
}
abstract class 휴대폰 extends 통신기기 {
카메라기능();
게임기능();
}
class 아이폰 extends 휴대폰 {
전원기능() { ... }
통화기능() { ... }
카메라기능() { ... }
게임기능() { ... }
애플 제품 연동기능() { ... }
}
// → 최종적으로 아이폰 class는 전원, 통화, 카메라, 게임, 애플 연동 5가지 기능을 정의하여 설계된다
인스턴스란 클래스를 통해서 구현해야할 대상(객체)이 실제로 구현된 구체적인 실체를 말한다.
클래스를 사용하여 힙 영역(Heap Area)에 새로운 인스턴스(객체)를 생성할 수 있다.
public static final 과 public abstract 제어자는 생략 가능하다.접근제어자 interface 인터페이스이름 {
public static final 타입 상수이름 = 값;
...
public abstract 메소드이름(매개변수목록);
...
}
public interface TV {
int MAX_VOLUME = 10; // public static final 생략 가능
int MIN_VOLUME = 10;
void turnOn(); // public abstract 생략 가능
void turnOff();
void changeVolume(int volume);
void changeChannel(int channel);
}
impliments 키워드를 쓴 후에 인터페이스를 작성💡 인터페이스에서 extends 키워드 대신 implements 라는 ‘구현’ 이라는 키워드를 사용하는 이유
💡인터페이스를 구현받고 추상 메서드를 구체적으로 구현할 때 접근제어자 설정에 주의해야 한다.
public abstract 가 생략된 상태이기 때문에 반드시 자식 클래스의 메서드 구현부에서는 제어자를 public 으로 설정해 주어야 한다.abstract 를 붙여서 추상 클래스로 선언해야 한다.interface Animal {
void walk();
void run();
void breed();
}
// Animal 인터페이스를 일부만 구현하는 포유류 추상 클래스
abstract class Mammalia implements Animal {
public void walk() { ... }
public void run() { ... }
// public void breed() 는 자식 클래스에서 구체적으로 구현하도록 일부로 구현하지 않음 (추상 메서드로 처리)
}
class Lion extends Mammalia {
@Override
public void breed() { ... }
}static 이기 때문에 구현체를 따라가지 않게 된다. (독립 상수)public static final 이기에, 서로 상속을 해도 독립적으로 운용interface Iflower {
int ex = 10; // 각각 public static final
}
interface IPlant extends Iflower {
int ex = 20; // 각각 public static final
}
class Tulip implements IPlant {
int ex = 30; // 그냥 인스턴스 변수
}
public class Main {
public static void main(String[] args) {
// 클래스 타입 객체로 ex 멤버에 접근하면, 클래스 인스턴스 변수로 접근
Tulip t = new Tulip();
System.out.println(t.ex); // 30
// 인터페이스 타입 객체로 멤버에 접근하면, 인터페이스 static 상수로 접근
Iflower a = new Tulip();
System.out.println(a.ex); // 10 - 좋지않은 방법
System.out.println(Iflower.ex); // 10 - 클래스 static 처럼 '인터페이스.멤버' 로 접근
IPlant b = new Tulip();
System.out.println(b.ex); // 20 - 좋지않은 방법
System.out.println(IPlant.ex); // 20 - 클래스 static 처럼 '인터페이스.멤버' 로 접근
}
}@implSpec 자바 doc 태그를 사용해 문서화 해줘야 한다)자바 doc 태그: JAVA 소스코드에서 API 문서를 html 태그형식으로 작성하게 해주는 도구
interface A1{
public void styleA();
// 메소드 시그니처가 같은 디폴트 메서드
default public void styleSame(){
System.out.println("A1 인터페이스의 디폴트 메서드 입니다.");
}
}
interface B1{
public void styleB();
// 메소드 시그니처가 같은 디폴트 메서드
default public void styleSame(){
System.out.println("B1 인터페이스의 디폴트 메서드 입니다.");
}
}
class MultiInterface implements A1, B1 {
@Override
public void styleA() {}
@Override
public void styleB() {}
// 두 인터페이스 디폴트 메서드중 A1 인터페이스의 디폴트 메서드를 오버라이딩 하여 구현
public void styleSame(){
System.out.println("A1 인터페이스의 디폴트 메서드 입니다.");
}
}
public class Main {
public static void main(String[] args) {
MultiInterface m1 = new MultiInterface();
m1.styleSame(); // "A1 인터페이스의 디폴트 메서드 입니다."
}
}인터페이스의 디폴트 메서드와 부모 클래스 메서드 간의 충돌
interface A1{
public void styleA();
// C1 클래스와 메소드 시그니처가 같은 디폴트 메서드
default public void styleSame() {
System.out.println("A1 인터페이스의 디폴트 메서드 입니다.");
}
}
abstract class C1 {
// A1 인터페이스와 메소드 시그니처가 같은 인스턴스 메서드
public void styleSame() {
System.out.println("C1 클래스의 인스턴스 메서드 입니다.");
}
}
// 메서드 시그니처가 같은 두 추상화들을 동시에 상속
class MultiClassInterface extends C1 implements A1 {
@Override
public void styleA() {}
}
public class Main {
public static void main(String[] args) {
MultiClassInterface m1 = new MultiClassInterface();
m1.styleSame(); // "C1 클래스의 인스턴스 메서드 입니다." - 클래스의 메서드 시그니처가 우선되어 적용됨
// 마찬가지로 인터페이스 타입으로 다운캐스팅 해도 클래스 인스턴스 메서드로 호출 됨
((A1) m1).styleSame(); // "C1 클래스의 인스턴스 메서드 입니다."
}
}
// 메서드 시그니처가 같은 두 추상화들을 동시에 상속
class MultiClassInterface extends C1 implements A1 {
@Override
public void styleA() {}
// 클래스의 인스턴스 메서드를 무시하고 인터페이스의 디폴트 메서드를 사용하기 위해 그대로 오버라이딩
public void styleSame() {
System.out.println("A1 인터페이스의 디폴트 메서드 입니다.");
}
}
public class Main {
public static void main(String[] args) {
MultiClassInterface m1 = new MultiClassInterface();
m1.styleSame(); // "A1 인터페이스의 디폴트 메서드 입니다."
}
}
객체는 인터페이스를 사용해 참조하라.
적당한 인터페이스가 있다면 매개변수뿐만 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라.
객체의 실제 클래스를 사용할 상황은 '오직' 생성자로 생성할 때 뿐이다.
매개변수 타입으로는 클래스 보다는 인터페이스를 활용하라.
인터페이스로 선언한 상황에서 다른 클래스로 변경하는 일이 생기는 경우 로직상의 문제가 없는지 반드시 확인해야 한다.
public abstract 로 정의 (default 메소드 제외)public static final 상수자유로운 타입 묶음
인터페이스 다형성 이용 설계
마커 인터페이스
instanceOf 를 이용하여 마커 인터페이스를 상속받은 클래스인지 판단할 수 있다.추상클래스의 다형성 이용 설계
public class ExamConsole {
Exam exam; // 상위 추상 클래스 타입으로 선언
// 생성자 매개변수로 new NewlecExam() 혹은 new YBMExam() 생성자가 들어와 필드를 초기화
ExamConsole(Exam e) {
this.exam = e; // 업캐스팅 초기화
}
void input() {}
void print() {}
}명확한 계층 구조 추상화
인터페이스는 확장성, 추상 클래스는 객체의 구조화 및 유지보수 용이성
| Abstract Class | Interface | |
|---|---|---|
| 사용 가능 변수 | final, non-final, static, non-static variables | static, final |
| 사용 가능 접근 제어자 | 제한 없음 | public |
| 사용 가능 메소드 | abstract method, non-abstract method | abstract method |
| 상속 키워드 | extends | implements |
| 다중 상속 가능 여부 | 불가능 | 가능 ( ex. A class implements B, C ) |
| 사용 키워드 | abstract | interface |
| 공통점 | 1. 인스턴스화 할 수 없다 ➡ 인터페이스 혹은 추상 클래스를 상속받아 구현한 구현체의 인스턴스를 사용한다 (혹은 위와 같은 명령문을 사용할 경우 Anonymous Class를 사용하여 모든 메소드를 재정의 해야 한다) 2. 구현 여부에 관계 없이 선언 및 정의된 메소드 집합을 포함할 수 있다 |
Q1. 마커 역할은 abstract 클래스로는 불가능한가요?
A1. 추상 클래스도 내부가 비어있도록 설정할 수 있기 때문에, 하나의 마커만 사용한다면 사용할 수 있을 것입니다! 일반 클래스도 역시 마찬가지 입니다. 하지만 추상클래스나 클래스의 경우 다중 상속이 불가능하기 때문에 여러 개의 타입을 확인해야하는 경우나 마커를 상속받은 클래스가 다른 추상 클래스 또는 일반 클래스를 상속 받고 있는 경우에는 부적절하다고 생각합니다!
Q2. 마커 인터페이스와 어노테이션의 차이에 대해서 찾아보세요.
A2.
마커 인터페이스와 마커 어노테이션의 차이
ObjectOutputStream.writeObject 의 경우에는 런타임 시점에 문제를 확인하기 때문에 예외적으로 마커 인터페이스의 장점을 살리지 못한 케이스이다.)ElementType.Type으로 선언한 어노테이션의 제약조건을 걸게 되면 이 어노테이션이 붙은 모든 코드에 제약조건이 붙게된다.마커 인터페이스의 사용
마커 어노테이션의 사용
Q3.
인터페이스는 결국 일종의 추상 클래스인가요? 추상 클래스와 인터페이스의 의도를 제외하고 코드만 놓고 봤을 때, 추상클래스에서는 선언 + 구현이 가능하고 인터페이스의 경우에는 선언만 가능하다는 차이가 있다고 알고 있습니다.
근데 자바 9 이후에 default 메서드를 허용했는데, 그러면 인터페이스에서도 선언과 구현이 같이 있는 것 아닌가요?
이 관점에서 둘의 차이가 무엇인지 궁금합니다. 만약 둘이 같지 않다면, 왜 자바 9에서 interface에 default 메서드를 허용했는지가 궁금합니다.
A3. Java8에서 default method가 등장하면서 추상 클래스와 인터페이스의 기능적인 명세가 비슷해진 것이 사실입니다!
추상 클래스에서 추상 메서드가 아닌 이미 구현된 메서드를 오버라이딩하지 않고 사용한다는 점에서 인터페이스의 defualt 메서드와 매우 흡사한 모습이기 때문입니다.
default 메서드가 등장한 이유는 ‘하위 호환성’이라는 개념 때문입니다. 간단히, 기존에 존재하던 인터페이스를 이용하여 이미 구현된 클래스들을 사용하고 있는 중, 인터페이스에 필수적인 메소드를 추가해야 할 경우를 떠올리면 이해하기 쉽습니다. default 메서드가 없는 경우 최상위 인터페이스에 새 메서드를 생성하게 되고 이로인해 이미 구현된 클래스와 호환성이 떨어지게 됩니다. 하지만 default 메서드를 추가하므로써 하위 호환성을 유지할 수 있게 되었습니다.
공감하며 읽었습니다. 좋은 글 감사드립니다.