하위 클래스에선 여러 개의 인터페이스를 구현할 수 있다(다중 인터페이스) → 자바의 단일 상속 기능을 보완
인터페이스를 상속 받아 클래스 구현하면 인터페이스의 모든 추상메소드를 구현해야 한다.
용도 : 기능만을 하위 클래스에 강제하기 위해서 사용
인터페이스 내에서 final 지정자를 사용하지 않고 변수 선언과 동시에 초기화하면 상수로 인식
추상 메서드는 abstract 지정자를 생략해도 된다.
예)
Car interface
Truck : 구현 클래스
SportsCar : 구현 클래스
CarTest
결과
인터페이스로 회원 관리 기능 구현
Member class
MemberImpl class
package sec03.ex01;
public class MemberImpl implements Member {
@Override
public void joinMember() {
//회원 등록 기능
}
@Override
public void searchMember() {
//회원 조회 기능
}
@Override
public void modMember() {
//회원 수정 기능
}
@Override
public void delMember() {
//회원 삭제 기능
}
private void encodeMemberInfo() {
//회원 정보 암호화 기능
}
private String decodeMemberInfo() {
//회원 정보 복호화 기능
return "복호화된 회원 정보";
}
}
+) 변수이름에 주목 : 한 눈에 뜻과 기능을 알 수 있어야한다.
다중 인터페이스
Test1 interface
Test2 interface
구현 클래스 : Child class : 두개의 인터페이스를 모두 구현해야 한다.
ChildTest class (실행 클래스)
→ 구현 클래스가 구현하는 인터페이스는 모두 업캐스팅 참조 변수로 사용할 수 있다.
그러나 업캐스팅 상태에선 인터페이스에 선언된 추상 메서드만 호출 가능하다 .
인터페이스 간의 상속
인터페이스와 인터페이스의 계층 구조는 상속 구조이다.
구현 클래스에선 인터페이스와 상속 관계인 부모 인터페이스의 추상 메서드도 반드시 구현해야 한다.
구현 클래스 타입 변수는 모든 메서드를 호출 할 수 있다. (업캐스팅된 변수를 주의! → 다운캐스팅 후 호출)
인터페이스 다중 상속
클래스와 달리 다중 상속을 허용한다.
default 메서드와 static 메서드
사용하지 않는 인터페이스의 기능을 구현 클래스에서 모두 구현해주어야한다는 불편함
→ JDK8부터 디폴트 메서드와 static 메서드 도입
→ static이 붙으면 간단하게 클래스 혹은 인터페이스 이름.메서드
+) 오버라이딩이 아니다. 오버라이딩은 static이 없는 일반 메서드에서나 가능
public interface A {
public static void mS() {
System.out.println("A의 이름으로");
}
}
public interface B extends A {
public static void mS() {
System.out.println("B의 이름으로");
}
}
public class C implements B {
public static void mS() {
System.out.println("Class의 이름으로");
}
}
public class Test {
public static void main(String[] args) {
A.mS();
B.mS();
C.mS();
}
default 메서드 : Java8부터 인터페이스에 default 키워드를 이용해 구현이 포함된 메서드를 정의할 수 있다.
public interface A {
public default void mD() {
System.out.println("박쥐 메소드!");
}
}
public interface B extends A {
public void mD() //추상메소드!
}
public class C implements B {
@Override
public void mD() {
System.out.println("알고보니 박쥐였어!");
}
}
public class Test {
public static void main(String[] args) {
C c = new C();
c.mD(); //알고보니 박쥐였어!
}
default 메소드는 일반메소드처럼 상속, 오버라이딩도 되고 다시 추상메소드도 되는 특이한 메소드!
다시 추상이 되는건 ‘진짜 다시 추상으로 바꾸는 것’은 아니고, default 메서드를 무시하고 다시 추상화하는 방식으로 이해하면 된다.
예:
A는 default를 이용해 기본 구현을 제공
→ B는 그것을 받아들이지 않는다(추상화)
→ 이렇게 되면 B를 구현하는 클래스는 반드시 오버라이딩을 해야한다.
→ A를 참조변수로 생성자를 만들어도 새롭게 오버라이딩된 메소드가 실행된다
내부 클래스와 내부 인터페이스
1.내부 클래스
웹 프로그래밍에선 거의 사용하지 않지만 안드로이드 앱이나 PC용 자바 프로그램에서 주로 화면에서 사용자가 발생시킨 이벤트를 처리하는 용도로 사용된다.
형식:
class 외부클래스명 { … class 내부클래스명 { … } … }
내부 클래스 종류
멤버 내부 클래스 : 외부 클래스의 멤버로 사용
로컬 내부 클래스 : 외부 클래스의 메서드 내에서 정의된다.
정적 내부 클래스 : static으로 지정된 내부 클래스
익명 내부 클래스 : 내부 클래스가 이름 없이 사용된다.
멤버 내부 클래스
멤버 내부 클래스는 외부 클래스의 멤버이므로 같은 클래스에 있는 다른 멤버들에 자유롭게 접근 가능
package sec01.ex01;
public class Outter {
private String name;
private int age;
public Outter(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
/*
* public String getAddress() { //외부 클래스에선 내부 클래스에 접근할 수 x
* return address;
*/
//내부 클래스
public class Inner {
private String address;
public Inner(String address) {
this.address = address;
}
public String getUserInfo() {
return "이름: " + name + ", 나이: " + age + ", 주소: " + address;
//내부 클래스의 메서드에서 외부 클래스 멤버에 자유롭게 접근가능
}
}
}
package sec02.ex01;
public class Outter {
public void startChat() {
class ChattingThread { //로컬 내부 클래스는 메서드 내에서 구현됨
public void sendData() {
System.out.println("메시지를 송신합니다.");
}
public void receiveData() {
System.out.println("메시지를 수신합니다.");
}
}
ChattingThread chat = new ChattingThread(); //내부 클래스 인스턴스 생성 후 데이터 송수신
chat.sendData();
chat.receiveData();
}
public static void main(String[] args) {
Outter outter = new Outter(); //외부 클래스 인스턴스 생성
outter.startChat(); //메서드 호출 시 로컬 내부 클래스 인스턴스가 생성된다.
}
}
정적 내부 클래스
static으로 지정된 클래스 : main() 메서드가 실행되면 즉시 클래스가 메모리에 생성됨
package sec03.ex01;
public class Outter {
private String name;
private int age;
public Outter(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
static class Inner {
private String address; //인스턴스 필드
static String tel; //정적 필드
public Inner(String address) {
this.address = address;
}
public static String getTel() { //정적 메서드
return tel;
}
public String getUserInfo() {
return "주소: " + address + "\n"
+"전화번호: " + tel;
}
}
}
정적 내부 클래스는 프로그램 실행 시 바로 메모리에 생성되므로
외부클래스명.내부클래스명.멤버명으로 바로 접근할 수 있다.
또한 new 연산자를 이용해 인스턴스 생성도 가능
실행 클래스
package sec03.ex01;
public class InnterTest {
public static void main(String[] args) {
Outter.Inner.tel = "010-1111-2222"; //static 멤버는 바로 접근
String tel = Outter.Inner.getTel();
//Outter.Inner.address = "010-1111-2222"; 인스턴스 멤버 필드는 바로 접근할 수 없다.
System.out.println("전화번호: " + tel);
Outter outter = new Outter("이순신", 17);
Outter.Inner inner = new Outter.Inner("세종시 세종구");
String name = outter.getName();
int age = outter.getAge();
System.out.println("고객 정보");
System.out.println("---------------------------");
System.out.println("이름: " + name);
System.out.println("나이: " + age);
System.out.println(inner.getUserInfo());
}
}
구분
내부 클래스 (non-static)
정적 내부 클래스 (static)
바깥 클래스 인스턴스 필요?
필요함
필요 없음
생성 방식
Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();
package sec04.ex01;
public class Button {
ButtonEventListener listener;
public void addButtonEventListener(ButtonEventListener listener) {
this.listener = listener;
}
public void btnClicked(String btnName) {
listener.btnClicked(btnName);
}
static interface ButtonEventListener { //내부 인터페이스
public void btnClicked(String btnName);
}
}
내부 인터페이스를 구현한 handler 클래스
package sec04.ex01;
public class ButtonEventHandler implement Button.ButtonEventListener{
@Override
public void btnClicked(String btnName) {
System.out.println(btnName + " 버튼을 클릭합니다.");
}
}
실행 클래스
package sec04.ex01;
public class ButtonTest {
public static void main(String[] args) {
Button button = new Button();
button.addButtonEventListener(new ButtonEventHandler());
button.btnClicked("등록하기");
button.btnClicked("수정하기");
}
}