인터페이스나 추상클래스는 하나의 기호다. 전원버튼의 모양을 보고 전자기기의 전원을 켜는 것과 같이 인터페이스나 추상클래스의 모양을 보면 어떻게 써야 할지 알 수 있다.
보통 설계 단계에서 인터페이스를 정의하고 개발단계에서 인터페이스를 구현한 클래스를 만든다. 중요한 것은 인터페이스를 구현한 클래스가 어떻게 되어있는지의 여부.
인터페이스나 추상 클래스의 메소드를 사용하는 사용자의 입장에서 내부 구현이 어떻게 되어 있는지는 별로 안궁금하고, 그냥 호출하고, 응답을 받으면 된다.
인터페이스와 추상클래스를 사용하는 이유
- 설계시 선언해 두면 개발할때 기능을 구현하는데만 집중할 수 있다.
- 개발자의 역량에 따른 메소드의 이름과 매개변수 선언의 격차를 줄일 수 있다.
- 공통적인 인터페이스와 추상클래스를 선언해 놓으면 선언과 구현을 구분할 수 있다.
인터페이스 실습
인터페이스는 실제 코드를 만들지 않더라도, 어떤 메소드들이 있어야 하는지 정의하려고 할때 인터페이스를 사용하면 된다.
인터페이스 선언부는 public class로 시작하지 않고, public interface로 시작한다. interface 내부에 선언된 메소드들은 몸통이 있으면 안된다.
public interface MemberService {
public void insertMember(MemberDTO memberDTO);
public void updateMember(MemberDTO memberDTO);
public void deleteMember(MemberDTO memberDTO);
}
그럼 이건 어떻게 쓰지?
public class MemberServiceImpl implements MemberService {
@Override
public void insertMember(MemberDTO memberDTO){
}
@Override
public void updateMember(MemberDTO memberDTO){
}
@Override
public void deleteMember(MemberDTO memberDTO){
}
}
구현코드 클래스 선언부에 보면 implements 키워드가 있다. 이 키워드 뒤에 위에서 만든 인터페이스가 있다.
만들어진 인터페이스를 적용한다고 할때는 클래스 선언문에 클래스 이름 뒤에 implements라는 키워드를 쓴 후 인터페이스를 '나열'하면 된다. implements는 상속받는 것이 아니라 해당 클래스에서 구현해야 하는 인터페이스들을 정의함으로써 클래스에 짐을 지어주는 것이다. 인터페이스를 구현하는 것은 상속이 아니기 때문에 여러개를 implements 할 수 있다.
주의사항
인터페이스를 구현 할 경우 implements 뒤에 인터페이스 이름을 나열할 경우에는 반드시 인터페이스에 정의된 메소드들의 몸통을 만들어 줘야 한다. 그렇지 않으면 컴파일이 되지 않는다.
또, 인터페이스도 컴파일 하면 .class 확장자를 가지지만, 생성자도 없고, 구현된 메소드도 없기 때문에 그대로 사용할수는 없다. 그럼 어떻게 쓰지?
상속관계의 형변환을 생각해 보자.
MemberService memberService = new MemberServiceImpl();
실제 타입이 인터페이스 타입이지만, 인터페이스 구현 클래스에는 인터페이스에 선언된 모든 메소드가 구현되어 있다. 때문에 인터페이스의 메소드를 호출하면 구현체의 메소드가실행된다.
인터페이스의 또 다른 용도는 외부에 노출되는 것을 정의해 두고자 할때 사용한다.
인터페이스의 구현체 입장에서 보면, 구현체를 직접 호출하지 않고 인터페이스를 통해서 호출하게된다.
추상클래스
인터페이스도 아니고 클래스도 아닌 추상클래스는 자바에서 마음대로 초기화 하고 실행할 수 없도록 되어있다. 그래서 그 추상 클래스를 구현해 놓은 클래스로 초기화 및 실행이 가능하다.
public abstract class AbstractMemberService(){
public abstract void insertMember(MemberDTO memberDTO);
public abstract void updateMember(MemberDTO memberDTO);
public abstract void deleteMember(MemberDTO memberDTO);
public void print(MemberDTO memberDTO) {
System.out.println("MemberDTO INFO >>>>>>>>>>>>>>>>>>>>>"+memberDTO.toString());
}
}
추상클래스는 선언시 class 라는 키워드 앞에 abstract라는 키워드를 사용한다. 몸통이 없는 메소드 선언문에는 abstract라는 키워드를 명시했다.
추상클래스는 abstract로 선언한 메소드가 하나라도 있을때 선언한다.
인터페이스와 달리 구현되어있는 메소드가 있어도 상관없다. (print)
추상클래스는 인터페이스처럼 implements키워드를 사용해 구현할 수 없다. 추상클래스도 인터페이스가 아닌 클래스이기 때문이다.
추상클래스에는 구현된 메소드가 있을수 있기 때문에 확장해서 사용한다고 이야기해줘야 한다.
public class MemberManageServiceImpl extends AbstractMemberService {
public void insertMember(MemberDTO memberDTO) {
}
public void updateMember(MemberDTO memberDTO) {
}
public void deleteMember(MemberDTO memberDTO) {
}
}
상속과 같이 extends 키워드를 사용하면 된다.
이때도 역시 abstract로 선언된 메소드들을 구현해줘야 컴파일 에러가 발생하지 않는다.
추상클래스는 왜 만들었을까?
인터페이스를 선언하다보니, 어떤 메소드는 미리 만들어 놓아도 전혀 문제가 없는 경우가 있다.
특히 아주 공통적인 기능을 미리 구현해 놓으면 많은 도움이된다.
클래스 인터페이스, 추상클래스의 차이 정리
| 인터페이스 | 추상(abstract) 클래스 | 클래스 | |
| 선언시 사용하는 키워드 | interface | abstract class | class |
| 구현 안된 메소드 포함 가능 여부 | YES(필수) | YES | NO |
| 구현된 메소드 포함가능 여부 | NO | YES | YES(필수) |
| static 메소드 선언 가능 여부 | NO | YES | YES |
| final 메소드 선언 가능 여부 | NO | YES | YES |
| 상속 가능 여부 | NO | YES | YES |
| 구현 가능 여부 | YES | NO | NO |
언제 인터페이스를 사용할지 추상클래스를 사용할지 구분하는게 중요하다.
상속과 관련해서 알아둬야하는 키워드가 있다. final
final 키워드는 위치에 따라 의미가 다르다.
- 클래스에 붙을때
- 메소드에 붙을때
- 변수에 있을때
먼저 클래스에 final 키워드가 있을때는 상속이 안된다는 의미.
public final class String implements ...
우리가 자주 쓰는 참조 자료형 String 클래스의 선언부에도 final 이 붙어있다.
클래스에 final을 선언하는 이유가 뭘까?
클래스를 조금이라도 변경하면 안된다는 것. 더 이상 확장해서는 안되고, 누군가 이 클래스를 상속받아서 내용을 변경해서는 안되는 클래스를 선언할때 final로 선언한다.
메소드에 final이 있을때는 메소드를 더이상 오버라이딩 할 수 없다는 것이다. 다른 개발자가 내가 만든메소드를 덮어씌워 사용하지 못하게 하는 것이다.
변수에 final은 클래스와 메소드와는 다르다.
먼저, 인스턴스 변수 또는 클래스 변수의 경우를 보자. final이 붙은 인스턴스 변수나 static으로 선언된 클래스 변수는 선언과 함께 값을 지정해야 한다. 생성자나 메소드에서 초기화 할려고 해도 할 수 없다. 중복되어 변수값이 선언될 수 있기 때문이다. 아래와 같은 형태로 final 변수는 선언할 수 없고,
final int num;
static final String name;
이렇게 선언해야 한다.
final int num = 1;
static final String name = "molt";
매개변수나 지역변수에 final을 붙일때는 초기화할 필요가 없다는 의미이다. 매개변수는 이미 초기화 된 상태로 넘어오는 값이고, 지역변수는 해당 메소드 안에서만 사용하기 때문에 아래와 같이 써도 괜찮다.
public void method(final int number){
final String name;
}
그러나 아래와 같이 쓸때는 에러가 발생한다.
public void method(final int number) {
final String name;
name="molt";
name="lazymolt";
number=3;
}
name 값을 molt로 넣어줄때까지는 괜찮다. 하지만 name값을 또 다시 lazymolt로 바꾸려고 하면 에러가 발생한다. 지역변수로 쓰는 final 변수도 재할당은 불가능하다. 매개변수도 마찬가지이다. 이미 초기화된 상태에서 넘어오는 final 매개변수는 메소드 안에서 재할당 할 수 없다.
왜 final을 쓸까? 중요한것은 final을 남발하면 안된다. 변하지 않는 값(1~12월)즉, 상수에 final을 붙이면 유용하다.
여기까지 기본 자료형의 final
참조자료형의 final도 같을까?
dto 클래스에 final을 붙이고 생성한 후 생성자로 다시 초기화 할려고 하면 에러 참조 자료형도 두번 이상 값을 할당하거나 새로 생성자를 사용하여 초기화 할수 없다. 아래처럼은 안된다는 말이다.
final MemberDTO memberDTO = new MemberDTO();
memberDTO = new MemberDTO();
근데 dto 인스턴스화 하고, 객체 안의 프로퍼티에는 값을 변경할 수 있다.
final MemberDTO memberDTO = new MemberDTO();
memberDTO.name = "molt";
memberDTO.email = "lazymolt@mail.com";
즉, final로 선언된 객체자체를 재할당하는 것은 불가능하지만, dto 객체 안의 변수들은 final이 아니기때문에 얼마든지 변경 가능하다는 의미이다. 클래스가 final이라고 해도 그 안의 변수, 메소드가 모두 final인 것은 아니다.
'Java' 카테고리의 다른 글
| [자바의 신] 클래스 안의 클래스 (0) | 2020.11.28 |
|---|---|
| [자바의 신] 예외 (0) | 2020.11.28 |
| [자바의 신] Object (0) | 2020.10.21 |
| [자바의 신] 상속 (0) | 2020.10.06 |
| [자바의 신] 접근제어자 (0) | 2020.10.02 |