인터페이스

추상 클래스가 발전된 개념

 

인터페이스는 프로그램 설계 시 프로그램 전체 구조의 일관성과 통일성을 부여하는데 사용됨
 

 

정의 : interface 키워드로 선언된 추상 클래스
  • 인스턴스 생성 불가능
  • 멤버로는 상수와 추상 메서드만 허용
  • 업캐스팅 참조 변수로 사용할 수 있다
  • 하위 클래스에선 implements(구현) 관계이다
  • 하위 클래스에선 여러 개의 인터페이스를 구현할 수 있다(다중 인터페이스)
    → 자바의 단일 상속 기능을 보완
  • 인터페이스를 상속 받아 클래스 구현하면 인터페이스의 모든 추상메소드를 구현해야 한다.
 
용도 : 기능만을 하위 클래스에 강제하기 위해서 사용
 
인터페이스 내에서 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;
			//내부 클래스의 메서드에서 외부 클래스 멤버에 자유롭게 접근가능
		}
	}

}
 

내부 클래스 인스턴스 생성

외부클래스타입 참조변수1 = new 외부클래스 생성자();
외부클래스명.내부클래스명 참조변수2 = 참조변수1.new 내부클래스 생성자();
 
 

로컬 내부 클래스

외부 클래스의 메서드 구현 블록 내에서 정의된 클래스
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();
Outer.Inner inner = new Outer.Inner();
바깥 클래스 멤버 접근 바깥 클래스의 인스턴스 멤버에 접근 가능 바깥 클래스의 정적 멤버에만 접근 가능
구분내부 클래스 (non-static)정적 내부 클래스 (stati
  • 내부 클래스는 참조변수1(outer)로 생성
  • 정적 내부 클래스는 외부 클래스(Outter)로 생성
 

 


2.내부 인터페이스 

클래스의 멤버로 인터페이스 선언
static으로 지정할 수 있다.
주로 화면에서 이벤트를 처리하는 용도로 사용
형식:
class 외부클래스명{

 static interface 인터페이스명 {
 상수;
 추상메서드();
 …
 }

}
 
Button
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("수정하기");
	}

}
 
왜 여기서 ButtonEventListener가 아니라 Handler가 들어갈까?
button.addButtonEventListener(new ButtonEventHandler());
→ 인터페이스는 설계도일 뿐, 생성자를 만들 수 없다
→ 대신 인터페이스를 구현한 클래스의 생성자는 만들 수 있다.
 
근데 만약 재사용할 필요가 없는 간결한 코드라면 익명객체를 써서 interface 생성자를 만들 수 있다.

 

 
 

익명객체 

자바 화면에서 발생하는 이벤트 처리 시 많이 사용되는 익명(anonymous) 객체
 
화면에서 버튼 클릭시 발생하는 이벤트 처리 클래스 인스턴스는 처리가 끝난 후 메모리만 차지한다는 문제점
→ 재사용할 필요가 없는경우 익명객체를 쓰면 유용하다.
코드가 간결하고 재사용하지 않는 경우
 
클래스 이름이 없다.
인터페이스타입 참조변수 = new 인터페이스() {…(구현)};
  • 구현 클래스가 필요 없다
  • 익명 클래스는 클래스 정의 + 인스턴스 생성을 동시에 하는 표현=> 하나의 "문장(statement)" 이기 때문에, 끝에 ;(세미콜론)을 붙여야 문장이 완성된다.
ButtonEventListener
package sec05.ex01;

public interface ButtonEventListener {
	public void btnClicked(String btnName);

}
 
ButtonEventHandler
package sec05.ex01;

public class ButtonEventHandler {
	Button button = new Button();
	ButtonEventListener listener = new ButtonEventListener() {	//인터페이스를 익명 객체로 구현
	@Override
	public void btnClicked(String btnName) {
		System.out.println(btnName + " 버튼을 클릭합니다.");
	}
	};
}
원래 인터페이스는 생성자를 못만들지만 익명객체 즉, 동시에 정의+구현까지 하는 경우 메모리를 차지하지 않는 인터페이스 생성자를 만들 수 있다!
 
Button
package sec05.ex01;

public class Button {
	ButtonEventListener listener;
	public void addButtonEventListener(ButtonEventListener listener) {
		this.listener = listener;
	}
	
	public void btnClicked(String btnName) {
		listener.btnClicked(btnName);
	}
}
 
실행클래스
package sec05.ex01;

public class ButtonTest {
	public static void main(String[] args) {
		ButtonEventHandler handler = new ButtonEventHandler();
		
		handler.button.btnClicked("등록하기");
		handler.button.btnClicked("수정하기");
	}

}
 
 
+) 팁
익명 객체가 사용되는 메서드의 매개변수와 지역변수는 반드시 final로 지정해야 한다. final로 지정하지 않으면 컴파일 시 자동으로 지정됨.
→ 익명객체에서 이들 변수에 접근해서 값을 변경하면 에러 발생

 

 

+ Recent posts