JAVA
- interface
- 문법적으로는 다중상속, 활용에서는 클래스 설계에 도움이 된다.
시나리오 A씨는 프로젝트의 실무 담당자이다. 이번 프로젝트의 필요 기능의 일부를 B씨에게 의뢰할 생각이다. 의뢰할 기능을 요약하면 1. 이름과 주문등록 번호를 저장하는 기능의 클래스가 필요하다. 2. 이 클래스에는 주민등록 번호를 기반으로 사람의 이름을 찾는 기능이 포함되어야 한다.
이 기능을 담당하는 메소드는 다음과 같이 정의하고자 한다.
- 주민등록번호와 이름의 저장 -> void addPersonalInfo(String name, String perNum)
- 주민등록번호를 이요한 검색 -> String searchName(String perNum)
작업 의뢰를 정리하고 보니 문제가 생겼다.
문제 1 “나도 프로젝트를 진행해야 하는데, B씨가 클래스를 완성할 때까지 기다릴수만은 없지 않나? 그리고 나중에 내가 완성한 결과물과 B씨의 결과물을 하나로 묶을 때 문제가 생기지 않기 위한 명확한 약속이 필요한데?”
문제2 “내가 요구한 기능의 메소드가 한 클래스가 아니면 어떡하지? B씨가 어떻게 만들건 나는 하나의 인스턴스로 모든 일을 처리하고 싶은데? B씨가 완성한 기능만 활용하고 싶다고!”
그래서 A씨는 상속과 메소드 오버라이딩을 기반으로 메소드에 대한 약속을 프로그램 코드상에서 규정하고 싶다. 그래서 추상 클래스를 정의하였다.
abstract class PersonalNumberStorage
{
public abstract void addPersonalInfo(String name, String perNum);
public abstract String searchName(String perNum);
}
추상 클래스를 B씨에게 전달하여 다음 기능을 완성해줄것을 부탁한다.
A씨가 만든 추상 클래스를 B씨가 구현하는 것으로 추상 클래스가 다리 역할을 한다.
추상 클래스의 메서드가 모두 abstract 선언 되었음으로 interface로 대체할 수 있다. 그렇게 되면 이 클래스는 아래의 특징을 지닌다.
- 인터페이스 내에 존재하는 변수는 무조건 public static final로 선언된다.
- 인터페이스 내에 존재하는 메소드는 무조건 public abstract로 선언된다.
interface PersonalNumberStorage
{
public abstract void addPersonalInfo(String name, String perNum); //좀 더 명확히 표현
public abstract String searchName(String perNum);
}
-
- 메소드 오버라이딩 과정에서의 접근제어 지시자
- 메소드를 오버라이딩 하는 과정에서 접근의 허용범위를 좁히는 방식으로는 접근제어 지시자를 변경할 수 없다. 그대로 유지하는 것이 일반적이며, 변경하더라고 허용범위를 넓히는 방식으로만 가능하다.
-
interface의 특징
-
다중상속
public interface MyInterface{ public void myMethod(); } public interface YourInterface{ public void yourMethod(); } class OurClass implements MyInterface, YourInterface{ public void myMethod(){ } public void yourMethod(){ } }
-
인터페이스간 상속 관계형성 가능
public interface SuperInterf{ public void subMethod(); } public interface SubInterf extends SuperInterf{ public void subMethod(); }
인터페이스의 상속을 표현하는데 있어서는 implements가 아닌 extends가 사용되었음에 주의하자.
-
-
- interface 기반의 상수표현
- 인터페이스 내에 존재하는 변수는 무조건 public static final로 선언된다.
public interface Week{ int MON = 1, TUE = 2, WED = 3, THU = 4, FRI = 5, SAT = 6, SUN = 7; }
-
자바인터페이스의 또 다른 가치!
class ClassPrinter{ public static void print(Object obj){ System.out.println(obj.toString()); } } class Point{ private int xPos, yPos; Point(int x, int y){ xPos = x; yPos = y; } public String toString(){ String posInfo = "[x = "+xPos+", y = "+yPos+"]"; return posInfo; } } class ImplObjectPrintln{ public static void main(String[] args){ Point pos1 = new Point(1, 2); Point pos2 = new Point(5, 9); ClassPrinter.print(pos1); ClassPrinter.print(pos2); } }
-
위의 소스를 수정하여 인터페이스 UpperCasePrintable을 구현하는 클래스의 인스턴스가 print 메소드의 인자로 전달되면 문자열을 전부 대문자로 출력한다.
interface UpperCasePrintable{ } class Upperprinter{ public static void print(Object obj){ if(obj instanceof UpperCasePrintable){ System.out.println(obj.toString().toUpperCase()); }else{ System.out.println(obj.toString()); } } }
- Inner 클래스
class OuterClass //Outer 클래스
{
...
class InnerClass //Inner 클래스
{
...
}
}
: 클래스 안에서 다른 클래스를 정의할 수 있다. 바깥쪽에 정의 된 클래스가 Outer클래스, 안쪽에 정의된 클래스를 InnerClass라고 한다.
class OuterClass //Outer 클래스
{
...
static class Nested //Nested 클래스
{
...
}
}
: 위처럼 Inner클래스를 static으로 선언할 수 있는데, 이렇게 선언이 되는 클래스를 가리켜 ‘Static Inner 클래스’ 또는 간단히 Nested 클래스라고 한다.
- Nested 클래스의 특징
class OuterClassOne
{
OuterClassOne()
{
NestedClass nst = new NestedClass(); //아래의 NestedClass 인스턴스 생성
nst.simpleMethod();
}
static class NestedClass{
public void simpleMethod(){
System.out.println("Nested Instance Method One");
}
}
}
class OuterClassTwo
{
OuterClassTwo(){
NestedClass nst = new NestedClass();
nst.simpleMethod();
}
private static class NestedClass{
public void simpleMethod(){
System.out.println("Nested Instance Method Two");
}
}
}
public class NestedClassTest {
public static void main(String[] args){
OuterClassOne one = new OuterClassOne();
OuterClassTwo two = new OuterClassTwo();
OuterClassOne.NestedClass nst1 = new OuterClassOne.NestedClass();
nst1.simpleMethod();
// OuterClassTwo.NestedClass nst2 = new OuterClassTwo.NestedClass();
// nst2.simpleMethod();
}
}
: OuterClassTwo 클래스 내에 정의되어 있는 NestedClass는 private로 선언되어 있기 때문에 OuterClassTwo 외부에서 인스턴스 생성이 불가능하다.
-
- InnerClass의 특징
- Inner 클래스의 인스턴스는 반드시 Outer 클래스의 인스턴스에 종속되어야 하기 때문에 Outer 클래스의 인스턴스 없이는 생성이 불가능하다.
class OuterClass{
private String myName;
private int num;
OuterClass(String name){
myName = name;
num = 0;
}
public void whoAreYou(){
num++;
System.out.println(myName+ " OuterClass" + num);
}
class InnerClass{
InnerClass() {
whoAreYou(); //Outer 클래스의 메서드에 접근
}
}
}
class InnerClassTest {
public static void main(String[] args){
OuterClass out1 = new OuterClass("First");
OuterClass out2 = new OuterClass("Second");
out1.whoAreYou();
out2.whoAreYou();
OuterClass.InnerClass inn1 = out1.new InnerClass();
OuterClass.InnerClass inn2 = out2.new InnerClass();
OuterClass.InnerClass inn3 = out1.new InnerClass();
OuterClass.InnerClass inn4 = out1.new InnerClass();
OuterClass.InnerClass inn5 = out2.new InnerClass();
}
}
-
주목할 몇 가지
- Outer 클래스의 인스턴스 생성 후에야 Inner 클래스의 인스턴스 생성이 가능하다.
- Inner 클래스 내에서는 Outer 클래스의 멤버에 직접 접근이 가능하다.
- Inner 클래스의 인스턴스는 자신이 속할 Outer 클래스의 인스턴스를 기반으로 생성된다.
-
Inner 클래스와 Nested 클래스의 장점
- 클래스를 논리적으로 묶는 수단이 된다.
- 클래스들을 논리적으로 묶다보니, 캡슐화가 증가하는 효과가 있다.
- 결과적으로 코드의 가독성이 향상되고, 유지보수성이 좋아진다.
-
- Local 클래스
- Inner 클래스와 비슷한 성격을 가진다. (위의 주목학 몇 가지의 특성과 같은 성격) 다만 메소드 내에 정의가 되고, 정의된 메소드 내에서만 인스턴스 생성과 참조변수의 선언이 가능하다는 특징이 있다.
class OuterClass{ ... public LocalClass createLocalClassInst(){ class LocalClass{ ... } return new LocalClass(); //이렇게 하고 싶지만...! } }
: LocalClass는 메소드 내에서만 의미가 통하기 때문에 반환형으로 선언도 힘들고, 되도록한다고 해도 참조변수를 받을 수 가 없다.
interface Readable{ public void read(); } class OuterClass{ private String myName; OuterClass(String name) { myName = name; } public Readable createLocalClassInst(){ class LocalClass implements Readable{ public void read() { System.out.println("Outer inst name : "+myName); } } return new LocalClass(); } } public class LocalClassTest { public static void main(String[] args){ OuterClass out1 = new OuterClass("First"); Readable localInst1 = out1.createLocalClassInst(); localInst1.read(); OuterClass out2 = new OuterClass("Second"); Readable localInst2 = out2.createLocalClassInst(); localInst2.read(); } }
: 위 예제에서 보이듯 Local 클래스가 정의되면 인스턴스 접근을 위한 인터페이스가 함께 정의되는 것이 일반적이다.
-
- Local 클래스와 매개변수
- Local 클래스는 메소드 내에 존재하기 때문에 매개변수와 지역변수에 접근이 가능하다. 단 지역변수와 final로 선언되는 매개변수에만 접근이 가능하다.
interface Readable{ public void read(); } class OuterClass{ private String myName; OuterClass(String name) { myName = name; } public Readable createLocalClassInst(final int instID){ class LocalClass implements Readable{ public void read() { System.out.println("Outer inst name : "+myName); System.out.println("Local inst ID : "+instID); } } return new LocalClass(); } } public class LocalClassTest { public static void main(String[] args){ OuterClass out1 = new OuterClass("First"); Readable localInst1 = out1.createLocalClassInst(111); localInst1.read(); OuterClass out2 = new OuterClass("Second"); Readable localInst2 = out2.createLocalClassInst(222); localInst2.read(); } }
: 왜 final만 가능할까? -> createLocalClassInst에서 LocalClass 인스턴스를 생성하고 반환한다. 여기서 매개변수로 들어온 instID가 final이 아니라면 지역변수의 소멸과정에서 사라진다. 그럼에도 read()메소드에서는 이 지역변수를 호출하고 있다. (헉! 소멸된 지역변수를 부르네?) 그렇기 때문에 컴파일러는 Local 클래스에서 접근하는 지역변수와 매개변수의 복사본을 Local 클래스가 항상 접근 가능하도록 만들어 둔다. 때문에 이 변수가 final로 선언되어야 한다. (Local 클래스에서 이 값을 변경할 수 없도록)
-
- Anonymous(익명) 클래스
- Local 클래스와 비슷한데, Anonymous 클래스는 이름이 없다. 왜냐면 Local 클래스는 메소드에서 생성되고 사라지는 클래스기 때문에 사실상 이름이 필요없기 때문이다.
interface Readable2{ public void read(); } class OuterClass2{ private String myName; public OuterClass2(String name) { myName = name; } public Readable2 createLocalClassInst(final int instID){ return new Readable2(){ public void read(){ System.out.println("Outer inst name : " + myName); System.out.println("Local inst ID : "+ instID); } }; } } public class LocalParamAnonymous { public static void main(String[] args){ OuterClass2 out = new OuterClass2("My Outer Class"); Readable2 localInst1 = out.createLocalClassInst(111); localInst1.read(); Readable2 localInst2=out.createLocalClassInst(222); localInst2.read(); } }
: 이렇게 인터페이스에 메소드를 채워 넣은 형식으로 정의되는 클래스를 가리켜 Anonymous 클래스라고 한다. +Anonymous의 생성자는 정의할 수가 없다.