21. 인터페이스와 추상 클래스
인터페이스와 추상 클래스에 대해 알아가보자.
Interface(= 인터페이스)
인터페이스는 사용 규칙을 정의해주는 틀이라고 보면된다.
인터페이스는 틀을 짜주는 도구이기 때문에 메서드 시그니처 형식</sapn>으로 작성을 한다.
1
2
public void add(Object value) // method signature 메서드 시그니처
{ - } // method body 메서드 바디
이렇게 메서드 시그니처 형식으로만 작성되고 바디는 작성되지 않은 형태</sapn>를 추상 메서드라고 하고 주로 인터페이스와 추상 클래스에 사용이 된다.
추상 메서드
인터페이스는 모든 메서드를 암묵적으로 추상 메서드로 사용하는데 추상 메서드는 기본 public이여야하고 abstract 키워드를 사용하여 선언해야한다.
1
2
3
4
5
6
7
8
public interface List{
public abstract void add(Object value); // 추상메서드는 기본 public 과 abstract를 포함한다.
public Object remove(int index); // 인터페이스는 모든 메서드를 암묵적으로 추상 메서드라고 본다.
abstract Object get(int index); // 그래서 public이나 abstract를 굳이 껴 넣지 않아도 오류가 나지 않고
Object[] toArray(); // 자동으로 public과 abstract를 포함하고 있다고 생각하면 된다
private void test(); // 이러한 형태는 오류를 일으킨다.
}
인터페이스 사용
위처럼 설정한 인터페이스를 사용하기 위해선…
- implemeantes ~ 를 사용하여 클래스에 받아와야하며
- @Override 주석을 통해 오버라이드해 사용해야한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
class ArrayList implemeantes List{
@Override
public void add(Object value){ ~ }
@Override
public Object get(int index) { ~ }
@Override
public Object remove(int index){ ~ }
@Override
public Object[] toArray(){ ~ }
}
만약 Override를 했는데
- 인터페이스에 관련된 추상 메서드 이름이 없는 경우
- 인터페이스에는 존재하는 추상 메서드를 본문 클래스에서 구현하지 않았을 경우 에러가 나게 된다.
인터페이스를 왜 쓰는건가
인터페이스는 틀이다. 인터페이스를 만들어두면 클래스의 규칙을 정의해두는건데…
이런 규칙이 있다면 추후 비슷한 기능의 클래스를 만들 때 일관성 있는 프로그래밍을 가져갈 수 있고
만약 해당 클래스를 상속받아 사용하는 서브 클래스에서 코드 변경없이 부모 클래스 변경만으로 간단하게 비슷하지만 다른 기능을 가져갈 수 있다.
우리의 실습 프로젝트에 대입해서 보자면 이런 느낌..?
instanceof 와 getClass
equals를 오버라이딩 할 때 인텔리J에서는 선택지를 제공해준다.
이 차이를 살펴보자
User를 상속받은 Manager class가 있다고 할때…
getClass
1 2 3 4 5
equals(Object object){ if(this.getClass() != obj.getClass()){ return false; } }
instanceof
1 2 3 4 5
equals(Object object){ if(obj instanceof User){ return false; } }
1
2
3
4
5
6
7
8
9
10
User u1 = new User();
String str = new String();
User u2 = new User();
Manager m = new Manager();
u1.equals(str); // getClass : F / instanceof : F
u1.equals(u2); // getClass : T / instanceof : T
u1.equals(m); // getClass : F / instanceof : T << 다른 결과가 나온다.
// 명확하게 해당 클래스인지를 확인하고 싶다면 getClass를 사용해 비교하는 것이 맞다. < 이 방법이 자주 사용된다.
추상 클래스
추상클래스는 인터페이스에 작성된 규칙이 어떤 곳이든 동일하게 바디가 작성이 될 때,
이 중복된 코드를 하나로 합쳐주기 위해 사용된다.
추상 클래스의 사용
1
2
3
4
5
6
7
8
9
// public class AbstractList implements List <- abstract가 붙지 않으면 List에 포함된 모든 메서드들이 정의 되지않아 에러를 일으킨다.
public abstract class AbstractList implements List { // 그래서 클래스 앞에 반드시 abstract가 붙어야한다.
protected int size = 0;
@Override
public int size() {
return size;
}
}
이렇게 만든 추상 클래스를 사용하기 위해선..
1
public class LinkedList extends AbstractList { - } // 추상 클래스를 상속 받으면 List 인터페이스와 추상 클래스를 모두 가지고 있는 클래스가 된다.
중첩 클래스
중첩 클래스 중 static nested class만 오늘 잠깐 알아보자.
LinkedList 클래스를 위해 Node 클래스를 하나 만들었는데 이 Node 클래스는 LinkedList에서만 사용이 된다.
내부적으로 가지고 있는 인스턴스 멤버도 없기 때문에 독립성을 가지기 위해 LinkedList로 옮긴 후 static을 붙여 중첩 클래스로 만들어 준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LinkedList extends AbstractList {
private Node first;
private Node last;
// Node 클래스는 위처럼 LinkedList에만 속하고 사용되기 때문에 안에 넣고 static을 붙여 독립성을 가져준다.
private static class Node {
Object value;
Node next;
public Node(Object value){
this.value = value;
}
}
}
짤지식
- UML(= Unified Modeling Language / 유니파이드 모델링 랭귀지)
클래스 멤버
클래스 멤버란?
클래스에 속하는 변수나 메서드를 의미한다.
- 클래스 멤버
- 필드(= 변수)
- static(= 클래스 필드)
- non-static(= 인스턴스 필드)
- 메서드
- static(= 클래스 메서드)
- non-static(= 인스턴스 메서드)
- 생성자
- 초기화 블록(initializer)
- static
- 인스턴스
- 중첩 클래스(= nested class)
- static
- non-static(= innerclass)
- 필드(= 변수)
인스턴스 필드
1
2
3
4
5
class A{
int v1;
boolean v2;
}
A obj = new A(); // 200 주소 -> heap에 올라감 200[ v1 = 0 | v2 = false ]
클래스 로딩
static field의 초기화 동작
com.eomcs.oop.ex03.Exam0691 예제 동작 확인
- 스태틱 필드는 클래스를 로딩할 때 모두 불러온다.
- 위 이미지에서 A 클래스에서
a += B.b
를 실행할 때 B클래스의 스태틱 필드를 모두 불러오면서 B클래스의b += A.a
를 먼저 실행 후 A 클래스의 문을 실행하게된다.