인터페이스와 내부클래스

주8·2023년 1월 16일

인터페이스(interface)

인터페이스는 정해진 규칙에 맞게 구현하도록 스펙을 정의하고 제시하는 데 사용되는(표준화) 기본 설계도이자 추상화를 달성하기 위한 메커니즘이다.

  • 하나의 클래스는 여러 개의 관련 없는 interface를 구현할 수 있어 다중 상속 기능을 지원할 수 있다. → 다형성 적용
  • 표준화 가능
    • 프로젝트에서 사용되는 구현클래스에 대한 기본 틀(기본 설계도)을 인터페이스로 정의하여 개발함으로써 일관되고 정형화된 개발을 위한 표준화가 가능하다.
    • 클래스 개발자와 클래스 사용자의 협업이 쉬워지고, 개발시간을 단축시킬 수 있다.
  • 개발자들에게 인터페이스를 기본 설계도로 해서 구현하게 하면 일관되고, 정형화된 프로그램 개발이 가능하다.
  • 인터페이스를 사용하면 클래스간의 관계를 느슨하게 유지할 수 있고, 클래스간의 관계가 느슨하면 다른 클래스로 교체할 때, 수정할 코드가 줄어든다(의존 역전 원칙: DIP).
    • 클래스가 인터페이스에 의존하게 만들면, 클래스와 인터페이스를 구현한 클래스들 간에는 느슨한 결합이 형성된다.
  • 정적 상수(static constants)와 추상 메서드(Abstract method)를 선언할 수 있으며, 추상클래스처럼 인스턴스로 생성할 수 없다.
  • Java8 버전부터 default method, static method를 선언할 수 있다.
  • Java9 버전부터 private method를 선언할 수 있다.
  • 강제적인 클래스 관계가 아닌 서로 관련 없는 클래스들이 동일한 interface에 선언된 기능을 구현하게 하고, 동일 interface type으로 묶을 수 있다.
  • 다중의 interface를 구현하게 되면 구현한 class는 구현한 interface type으로 선언하여 사용할 수 있다.
  • 대표적인 예: JDBC API interface Connection, Statement 등

JDBC API interface

  • 특정 메서드를 사용하기 위해 호출하는 쪽은 사용하려는 메서드(Provider)의 메서드 선언부만 알면 된다.
  • 자동차 제조사가 많지만 핸들과 기어 사용법은 동일한 것과 같이 인터페이스 abstract method는 핸들과 기어만 노출하고 실제 구현은 제조사가 구현하도록 하는 것과 같다.
  • 데이터 저장소로 사용하는 데이터베이스는 다양한데 데이터베이스에 연결하는 방법, 쿼리하는 방법이 다양하다.
    • 기능을 제공하는 메서드 선언부의 묶음을 인터페이스로 정의하고 데이터베이스 벤더(Oracle, MySQL 등)가 인터페이스를 구현하여 JDBC Driver라는 구현체를 제공하도록 하고 개발자는 인터페이스의 메서드를 통해서만 데이터베이스를 활용하도록 표준화한 것이다.

interface 작성

  • 구현 방식은 Abstract와 유사하다.
  • class 대신 interface keyword를 사용한다.
  • 멤버는 추상 메서드와 정적 상수만 가능하다(java 8 이전).
    • Java 8 버전부터 default method, static method를 선언할 수 있다.
    • Java 9 버전부터 private method를 선언할 수 있다.
  • 추상클래스처럼 인스턴스를 생성할 수 없다.
  • 모든 데이터 멤버는 public static final이며 이 부분을 생략할 수 있다.
    • 생략하면 java 컴파일러가 추가하며, 기본적으로 공개된 정적 상수이다.
  • 모든 메서드는 public abstract이어야 하며 이 부분을 생략할 수 있다.
    • 생략하면 java 컴파일러가 추가하며, 기본적으로 공개된 추상 메서드이다.
  • interface와 interface가 상속관계를 가질 수 있으며 ‘extends’를 사용한다.
  • class가 interface를 구현할 때는 ‘implements’ 키워드 뒤에 interface명을 지정한다.
  • 인터페이스는 Object 클래스와 같은 최상위 인터페이스가 없다.
  • 하나의 클래스는 ‘is a’ 관계의 클래스 상속(extends)과 인터페이스 구현(implements)이 동시에 가능하다.
  • extends 절은 implements보다 먼저 선언되어야 한다.
  • interface를 implements로 선언한 클래스는 인터페이스에 정의된 추상메서드를 구현해야 한다.
interface 인터페이스이름{
	public static final 데이터타입 상수이름 =;
	public abstract 메서드이름(매개변수 목록);
}

interface Human{
	void move();
	void speak();
}

interface Person extends Human(){}

class Manager extends Employee implements Human{
	public void move(){}
	public void speak(){}
}

interface 다형성

  • 인터페이스 타입을 참조 변수로 선언할 수 있다.
  • 인터페이스 메서드 선언시 Argument 타입이나 return type으로 저장할 수 있다.
class Manager extends Employee implements Human{
	public void move(){}
	public void speak(){}
}

Employee e = new Manager();
Human h = new Manager();

//Human 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 받는 메서드 정의
void work(Human h){
	...
}

interface 예시

Flyer라는 interface를 선언하고 Airplane에서 Flyer를 implements하였다.

public interface Flyer{
	public void takeOff();
	public void land();
	public void fly();
}

public class Airplane implements Flyer{
	@Override
	public void takeOff(){
		System.out.println("비행기가 이륙하다");
	}
	@Override
	public void land(){
		System.out.println("비행기가 착륙하다");
	}
	@Override
	public void fly(){
		System.out.println("비행기가 날다");
	}
}

Flyer라는 interface에 기능을 선언하여 관련(상속관계가 아닌) class를 interface type으로 묶을 수 있다.
fly()라는 기능은 서로 관련 없는 클래스지만 비행기도, 새도, 슈퍼맨도 할 수 있다.
Flyer하는 하나의 type으로 선언되어 사용될 수있다.

  • A개발자가 인터페이스로 기능을 정의(스펙)하고, B개발자가 이 인터페이스를 구현하여 다른 개발자들에게 제공한다.
  • C개발자는 draw() 메서드가 어떻게 구현되어 있는지 알 필요 없이 제공된 구현 클래스를 활용하여 비즈니스를 개발한다.
  • B개발자는 스펙에 맞게 기능을 제공하고, C개발자는 이를 사용하는 서로 간의 계약을 맺는 것
interface Drawable{
	void draw();
}

class Rectangle implements Drawable{
	public void draw(){
		System.out.println("직사각형 그리기");
	}
}

class Circle implements Drawable{
	public void draw(){
		System.out.println("원 그리기");
	}
}

public class TestInfDrawable{
	public static void main(String[] args){
		Drawable d = new Circle();
		d.draw();
	}
}

  • 상속(extends)과 interface의 implements는 Mix되어서 많이 사용된다.
  • Animal super class를 상속하고, Flyer implements하여 구체적 기능을 구현한다.
public class Bird extends Animal implements Flyer{
	
	@Override
	public void takeOff(){ //interface implements
		System.out.println("새가 이륙하다");
	}
	@Override
	public void land(){ //interface implements
		System.out.println("새가 착륙하다");
	}
	@Override
	public void fly(){ //interface implements
		System.out.println("새가 날다");
	}
	public void buildNest(){
		System.out.println("새가 둥지를 짓다");
	}
	public void layEggs(){
		System.out.println("새가 알을 낳다");
	}
	@Override
	public void eat(){ //Animal eat() override
		System.out.println("새가 먹는다");
	}
}

interface에 의한 다중 상속(Multiple inheritance)

  • 클래스가 여러 인터페이스를 구현하거나 인터페이스가 여러 인터페이스를 확장하는 경우 이를 다중 상속이라고 한다.
  • 추상화된 기능을 여러개의 인터페이스로 분리하여 특정 클래스에서 원하는 특정 기능만 조합하여 구현할 수 있다.
  • 구현한다는 것은 인터페이스의 추상화된 기능을 제공하겠다는 것이다.
  • 인터페이스 타입의 참조변수의 구현한 클래스의 객체 참조값을 할당하여 사용할 수 있는데 클래스가 여러 인터페이스를 구현한 경우 선언한 타입의 인터페이스 메서드만 호출 가능하다.
interface Printable{
	void print();
}
interface Showable{
	void show();
}
public class MultipleInfTest implements Printable, Showable{
	@Override
	public void print(){
		System.out.println("안녕!");
	}
	@Override
	public void show(){
		System.out.println("반가워");
	}
	public static void main(String[] args){
		MultipleInfTest obj = new MultipleInfTest();
		obj.print();
		obj.show();

		Printable p = new MultipleInfTest();
		Showable s = new MultipleInfTest();
		p.print();
		//p.show(); <- Printable 인터페이스 타입의 메서드만 호출 가능
		s.show();
	}
}

디폴트 / 정적 / private 메서드

  • 인터페이스에 디폴트 메서드, static 메서드를 추가 가능하게 변경되었다. (JDK 1.8)
  • 클래스와 달리 인터페이스에 새로운 메서드(추상메서드)를 추가하면 하위 호환성 문제가 발생하여 추가가 어려웠다.
    • 해당 인터페이스를 구현한 클래스가 추가된 메서드를 모두 구현해야 하기 때문이다.
  • Java 8 버전부터는 이러한 문제점을 해결하기 위해 디폴트 메서드를 사용할 수 있고 정적 메서드도 사용할 수 있게 되었다.
  • Java 9 버전부터는 디폴트 메서드나 정적 메서드에서만 사용할 수 있는 private, private static 메서드를 사용할 수 있다.
  • 디폴트 메서드(default method)
    • 인터페이스에 추가된 일반 메서드(인터페이스 원칙 위반)로 상속되는 메서드이고, 인터페이스에 자유롭게 새로운 메서드를 추가할 수 있게 되고, 호환성을 유지하면서 API를 변경할 수 있다. 구현 클래스에서 재정의 할 수 있다.
  • 정적 메서드
    • static 메서드로 인터페이스명.메서드명()으로 구현 클래스 객체 생성없이 호출할 수 있다.
  • private 메서드
    • 인터페이스 내에서만 사용 가능한 메서드로 디폴트 메서드나 정적 메서드에서 사용하기 위해 구현하는 메서드로 default method의 동일 코드를 private method로 추출하여 재사용할 수 있도록 코드를 개선할 수 있고, 원하지 않게 상속받은 메서드를 오버라이드하는 오류를 제거할 수 있다.
  • 호환성을 유지한다는 것은 인터페이스에 디폴트 메서드나 정적 메서드가 추가되어도, 클래스의 소스 변경 없이 동작할 수 있음을 의미한다.
  • 잘 사용하지 않는 메서드를 디폴트 메서드로 구현하여 불필요한 코드를 줄일 수 있다.
interface Drawable{
	void draw();
	default void msg(){
		System.out.println("interface default method");
		privateMsg();
	}
	static void staticMsg(){
		System.out.println("interface static method");
		privateStaticMsg();
	}
	private void privateMsg(){
		System.out.println("\t-> interface private method");
	}
	private static void privateStaticMsg(){
		System.out.println("\t-> interface private static method");
	}
}
class Rectangle implements Drawable{
	@Override
	public void draw(){
		System.out.println("Rectangle class draw method");
	}
}
public class TestInfDefaultMethod{
	public static void main(String[] args){
		Drawble d = new Rectanle();
		d.draw();
		d.msg();
		Drawable.staticMsg();
	}
}

[결과]
Rectangle class draw method
interface default method
						-> interface private method
interface static method
						-> interface private static method

마커 인터페이스(Marker interface)

  • 멤버가 정의되지 않은 인터페이스는 마커 또는 태그가 지정된 인터페이스라고 한다.
  • 이런 인터페이스는 객체에 대한 런타임 유형 정보를 제공하므로 컴파일러와 JVM은 객체에 대한 추가 정보를 갖게 된다.
  • 예를 들어 Serializable 인터페이스의 경우 이 인터페이스를 implements로 선언한 클래스의 객체는 직렬화 할 수 있다는 것을 의미한다.
  • Cloneable의 경우에도 이 인터페이스를 implements로 선언한 클래스의 객체는 복제할 수 있음을 의미한다.
  • 예) java.io.Serializable, java.lang.Cloneable 등
public interface Serializable{
}

public interface Cloneable{
}

중첩 인터페이스(Nested interface)

  • 다른 인터페이스나 클래스내에서 선언된 인터페이스를 중첩 인터페이스라고 한다.
  • 관련 인터페이스를 그룹화하는데 사용되므로 쉽게 유지관리할 수 있고, 코드의 가독성에 좋다.
  • 외부 인터페이스 또는 클래스에서 참조해야 하고 직접 액세스 할 수 없다.
  • 중첩 인터페이스는 인터페이스 내에서 선언된 경우 public이어야 하지만 클래스 내에서 선언된 경우 모든 접근제어자(Access modifier)를 선언할 수 있다.
  • 중첩 인터페이스는 static으로 선언된다.
  • 클래스내세 중첩 인터페이스를 선언하는 이유는 해당 클래스와 긴밀한 관계를 맺는 구현 클래스를 만들기 위함이다.
  • 인터페이스 내에서 선언
interface interface_name{
	interface nested_interface_name{
	
	}
}
  • 클래스 내에서 선언: Android View 클래스의 예로 이벤트를 처리하는 구현 클래스를 만들 수 있도록 중첩 인터페이스를 선언하고 있다.
class class_name{
	interface nested_interface_name{

	}
}

public class View{
	public interface OnClickListener{
		public void onClick(View view);
	}
}
  • 예제

중첩 인터페이스에 직접 접근할 수 없기 때문에 Showable 인터페이스로 Message 인터페이스에 접근하고 있다.

interface Showable{
	void show();
	//중첩 인터페이스 Message
	interface Message{
		void msg();
	}
}
class TestNestedInterface implements Showable.Message{
	@Override
	public void msg(){
		System.out.println("안녕! nested interface");
	}

	public static void main(String[] args){
		Showable.Message message = new TestNestedInterface();
		message.msg();
	}
}

javap로 확인해보면 NestedMember로 표기된다.


#1 = Class #11 // ch11/nestedinf/Showable
#3 = Class #13 // ch11/nestedinf/Showable$Message

NestedMembers:
ch11/nestedinf/Showable$Message


클래스 내에서 인터페이스를 정의하는 방법은 인터페이스 내에서 정의하는 방법과 크게 차이는 없다.

class A{
	interface Message{
		void msg();
	}
}

class TestNestedInterface implements A.Message{
	public void msg(){
		System.out.println("안녕! nested interface");
	}

	public static void main(String[] args){
		A.Message message = new TestNestedInterface();
		message.msg();
	}
}

javap로 확인해보면 NestedMember로 표기된다.


#7 = InterfaceMethodref #9.#25 // ch11/nestedinfclass/A$Message.msg:()V
#9 = Class #38 // ch11/nestedinfclass/A$Message

9:invokeinterface #7, 1 // interfaceMethod ch11/nesatedinfclass/A$Message.msg:()V


추상 클래스와 인터페이스의 차이점

추상 클래스(abstract class)인터페이스(interface)
추상 메서드와 일반 메서드를 같이 가질 수 있다.추상 메서드만 선언할 수 있었으나 java 8 버전부터는 default, static, private 메서드도 선언할 수 있다.
다중 상속을 지원하지 않는다.다중 상속을 지원한다.
final, non-final, static, non-static 변수를 가질 수 있다.static 및 final variable만 가질 수 있다.
인터페이스의 구현을 제공할 수 있다.추상 클래스의 구현을 제공할 수 없다.
abstract 키워드는 추상 클래스를 선언하는데 사용된다.interface 키워드는 인터페이스를 선언하는데 사용된다.
추상 클래스는 다른 클래스를 상속하고 여러 인터페이스를 구현할 수 있다.다른 인터페이스를 상속만 할 수 있다.

내부클래스(inner class)

  • JDK 1.1부터 추가된 기능으로 내부클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있고, 코드의 복잡성을 줄인다.
  • 클래스 정의시 클래스 내부에 또 다른 클래스를 정의하여 사용한 것이다.
  • 클래스의 멤버라고 볼 수 있다. (variable / method / constructor / inner class)
  • 클래스의 scope내에서 접근할 수 있으며, 중첩 클래스(nested class)라고도 한다.
  • inner class는 abstract로 정의할 수 있으며 UI 구현시 Event Handling에서 많이 사용한다.
  • inner class는 class파일이 Top-Level “Class명$InnerClass명” 형태로 생성된다.
  • inner class내에 메소드를 구현하게 되면 아래와 같이 접근하여 실행
    • Top-Level의 인스턴스를 생성 → Inner 클래스 생성 → Inner인스턴스명.메소드명()으로 접근
    • 예)
      Outer outer = new Outer();
      Outer.Inner inner = outer.new Inner();
      inner.innerMethod();
    • 컴파일로 생성된 class파일은 각각
      InnerEx4.class
      Outer.class
      outerInstanceInner.classouterInstanceInner.class outerStaticInner.class
      outer$1LocalInner.class

Java 컴파일러는 내부 클래스의 경우 두 개의 클래스 파일을 생성하며, 내부 클래스를 인스턴스화 하려면 외부 클래스의 인스턴스를 만들어야 하며, 이 경우 외부 클래스의 인스턴스 내부에 내부 클래스의 인스턴스가 생성된다.

컴파일러는 Outer$Inner 형식의 클래스 파일을 생성하는데, 멤버 내부 클래스에는 Outer 클래스의 참조가 있으므로 Outer 클래스의 모든 데이터 멤버에 접근할 수 있다.


내부클래스 장점

  • Outer 클래스의 private을 포함한 모든 멤버에 접근할 수 있다.
  • 논리적으로 클래스와 인터페이스를 한 곳에만 그룹화하므로 가독성이 좋고 유지관리가 쉬운 코드를 개발하는데 사용된다.
  • 더 적은 코드로 기능을 구현할 수 있다.

유형

  • 멤버 내부클래스(Member inner class): 클래스 내부와 메서드 외부에서 생성된 클래스
  • 익명 내부클래스(Anonymous inner class): 인터페이스를 구현하거나 클래스를 확장하기 위해 만든 클래스
  • 로컬 내부클래스(Local inner class): 메서드 내에서 클래스가 생성된다.
  • 정적 중첩 클래스(Static nested class): 클래스 내에 정적 클래스가 생성된다.
  • 중첩 인터페이스(Nested interface): 클래스 또는 인터페이스 내에서 생성된 인터페이스

예제

  • Outer class의 member variable을 inner class의 메소드에서 접근하여 사용할 수 있다.
public class Outer1{
	private int size;

	/* Declare an inner class called "Inner" */
	public class Inner{
		public void doStuff(){
			//The inner class has access to 'size' from Outer
			size++;
		}
	}

	public void testTheInner(){
		Inner i = new Inner();
		i.doStuff();
	}
}

  • public inner 클래스를 Outer class의 instance를 이용하여 생성하는 예제다.
public class Outer2{
	private int size;

	public class Inner{
		public void doStuff(){
			size++;
		}
	}
}

public class TestInner{
	public static void main(String[] args){
		Outer2 outer = new Outer2();

		//Must create an Inner object relative to an Outer
		Outer2.Inner inner = outer.new Inner();
		//반드시 종속되어 있는 클래스를 먼저 명시해 주어야 한다.
		//Outer를 통해서만 Inner class에 접근할 수 있다.
		inner.doStuff();
	}
}

  • 아래의 예제는 inner class의 member의 접근 방식을 보여준다.
  • size라는 변수명은 동일하나 outer class의 멤버인지 inner class의 멤버인지를 구분하여 접근하기 위한 방법을 제공한다.
public class Outer3{
	private int size;
	
	public class Inner{
		private int size;

		public void doStuff(int size){
			size++; //the local parameter
			this.size++; //the Inner object attribute
			Outer3.this.size++; //the Outer3 object attribute
		}
	}
}

  • inner class에서 메소드를 정의했을 경우 inner class에서 outer class의 member variable에 접근하게 하려면 outer class의 변수가 final로 선언되어야만 한다.
  • final로 선언되지 않은 변수들은 메소드가 종료되면 메모리에서 제거되므로 사용할 수 없다.
  • final 변수는 Constant Pool이라는 메모리 저장공간에 저장된다. 즉, 변수의 scope가 다르다.
  • 이 규칙은 java 1.7버전까지이며, 이후에는 final 선언이 필요 없다.
public class Outer4{
	private int size = 5;

	public Object makeTheInner(int localVar){
		final int finalLocalVar = 6;

		//Declare a class within a method!
		class Inner{
			public String toString(){
				return ("#<Inner size=" + size +
								// " localVar=" + localVar + //ERROR: ILLEGAL
								"finalLocalVar=" + finalLocalVar + ">");
			}
		}

		return new Inner();
	}

	public static void main(String[] args){
		Outer4 outer = new Outer4();
		Object obj = outer.makeTheInner(47);
		System.out.println("The object is " + obj);
	}
}

[결과]
The Object is #<Inner size=5 finalLocalVar=6>

익명 내부클래스(Anonymoue inner class)

  • 이름이 없는 일회용 클래스. 단 하나의 객체만을 생성할 수 있다.
  • 재사용이 필요없고, UI 이벤트 처리와 같은 단발성의 역할로 사용한다.
  • 클래스는 “자바파일명${익명객체정의된순번}.class” 형태로 생성된다.
  • 만약 A 클래스에 익명으로 2개를 정의했다고 가정하면 A.class, A$1.class, A$2.class 3개의 클래스 파일이 생성된다.
public abstract class MyInterface{
	public abstract void doSomething();
}

//인터페이스 구현과 동시에 객체 생성
MyInterface myClass = new MyInterface(){
	@Override
	public void doSomething(){
		System.out.println("doSomething");
	}
};

myClass.doSomething();

로컬 내부클래스(Local inner class)

  • 매서드 내에서 생성되는 클래스이며, 메서드 블록내에 정의되며, 클래스의 멤버는 아니다.
  • 로컬 내부크래스는 메서드 블록내에서만 범위가 유지된다.
  • 이 클래스는 클래스의 멤버 변수에 접근할 수 있다.
  • 메서드 내에서 클래스 내부 클래스의 메서드를 호출하려면 이 클래스를 인스턴스로 생성해야 한다.
public class LocalInnerEx1{
	//인스턴스 멤버 변수
	private int date = 30;

	void display(){
		class Local{
			void msg(){
				System.out.println(data);
			}
		}
		Local l = new Local();
		l.msg();
	}

	public static void main(String[] args){
		LocalInnerEx1 obj = new LocalInnerEx1();
		obj.display();
	}
}

[결과]
30

정적 중첩클래스(Static nested class)

  • Non-static 멤버 데이터 및 메서드를 접근할 수 없으며, Outer 클래스 이름으로 접근할 수 있다.
  • Outer 클래스의 static 변수에 접근할 수 있다.
public class StaticInnerEx1{
	static int data=30;
	static class Inner{
		void msg(){System.out.println("data is "+data);}
	}
	public static void main(String[] args){
		StaticInnerEx1.Inner obj = new StaticInnerEx1.Inner();
		obj.msg();
	}
}

[결과]
30
profile
웹퍼블리셔의 백엔드 개발자 도전기

0개의 댓글