본문 바로가기

개발/Java

17 abstract 클래스

반응형

"완전하지 않는" 뜻으로 이해할 것 

완벽한 abstract 클래스는 interface
앞서 정의한 PersonalNumberStorage 클래스는 abstract 메소드로만 이뤄져 있는데,
이러한 경우에는 다음과 같이 인터페이스의 정의로 abstract클래스의 정의를 대체할 수 잇다. 

interface PersonalNumberStorage {
   int a;
   void addPersonallInfo(String perNum, String name);
   String searchName(String perNum);
}
위의 인터페이스는 다음의 특징을 지니는 특별한 클래스로 볼 수 잇다. 
- 인터페이스 내에 존재하는 변수는 무조건 public static final 로 선언된다. 
- 인터페이스 내에 존재하는 메소드는 무조건 public abstract로 선언된다. 

즉 위의 인터페이스 정의는 다음의 클래스 정의와 완전히 동일하다. 
abstract class PersonalNumberStorage {
   public static final a;
   public abstract void addPersonalInfo(String perNum, String name);
   public abstract String searchName(Steing perNum);
}

인터페이스 역시 참조변수의 선언도 가능하고 특히 메소드의 오버리이딜도 그대로 적용된다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package interface_;
 
interface PersonalNumberStorage {
    void addPersonalInfo(String perNum, String name);
    String searchName(String perNum);
}
 
class PersonalNumInfo {
    
    String name;
    String number;
    
    PersonalNumInfo(String name, String number)    {
        this.name= name;
        this.number= number;
    }
    
    String getName() {return name;}
    String getNumber() {return number;}
}
 
 
class PersonalNumberStorageImpl implements PersonalNumberStorage {
 
    PersonalNumInfo[] perArr;
    int numOfPerInfo;
    
    public PersonalNumberStorageImpl(int sz) {
        perArr = new PersonalNumInfo[sz];
        numOfPerInfo=0;
    }
 
    public void addPersonalInfo(String perNum, String name) {
        perArr[numOfPerInfo]=new PersonalNumInfo(name, perNum);
        numOfPerInfo++;
    }
 
    public String searchName(String perNum) {
        for (int i = 0; i < numOfPerInfo; i++) {
            if (perNum.compareTo(perArr[i].getNumber())==0) {
                return perArr[i].getName();
            }
        }
        return null;
    }
}
 
public class AbstractInterface2 {
    public static void main(String []args)
    {
        PersonalNumberStorage storage = new PersonalNumberStorageImpl(100);
        storage.addPersonalInfo("950000-1122333""김기동");
        storage.addPersonalInfo("970000-1122334""장산길");
        System.out.println(storage.searchName("950000-1122333"));
        System.out.println(storage.searchName("970000-1122334"));
    }
}
   
cs


일반적으로 클래스가 인터페이스를 상속하는 경우에는 '상속'이라는 표현을 쓰지 않고, '구현'이라는 표현을 쓴다. 하위 클래스에서 인터페이스에 정의된 텅 빈 메소드(abstract메소드)를 구현해서 채워 넣어야 하기 때문이다. 그래서 인터페이스를 구현(상속)랑 때에는 키워드 extends를 사용하지 않고, 키워드 implement를 사용한다.


interface의 특성
public interface MyInterface { // 인터페이스는 public으로 선언하는 것이 일반적
   public void myMEthod();     // public이 아니어도 public abstract로 선언됨
}

public interface YourInteface {
   public void youtMethod();
}
 위와 같이 두 개의 인터페이스가 정의되어 있을때, 클래스는 위의 두 메소드를 동시에 구현할 수 잇다. 
Class OurClass implements MyInterface, YourInterface {
   public void myMethod() {...}
   public void yourMethod() {...}
}
그리고 인터페이스간에도 상속 관계를 형성할 수 있다. 즉 다음과 같은 인터페이스간 상속이 가능하다. 
public interface SuperInterface {
   public void superMethod();
}

public interface SubIntef extends SuperInterf {  // extends가 사용되었음
  public void subMethod();
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package interface_;
 
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;
    }
}
 
 
public class ImplObjectPrintln {
 
    public static void main(String[] args) {
        Point pos1 = new Point(12);
        Point pos2 = new Point(59);
        
        ClassPrinter.print(pos1);
        ClassPrinter.print(pos2);
    }
}
 
cs


4행 : 여기 정의된 println 메소드는 매개변수형이Object이다. 따라서 모든 인스턴스의 참조값은 이 메소드의 전달인자가 될수 있다. 

16행 : toString메소드는 Object클래스에 정의되어 있는 메소드이다. 그리고 objct 클래스는 모든 클래스가 상속하는 상위 클래스이다. 따라서 이는 Object클래스에 정의되어 있는 메소드의 오버라이딩이다. 

위 예제에서 정의하고 있는 print메소드에 다음의 기능을 추가하고자 한다. 
" 인터페이스 UpperCasePrintable을 구현하는 클래스의 인스턴스가 print메소드의 인자로 전달되면 문자열을 전부 대문자로 출력한다."


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
 
package interface2;
 
 
interface UpperCasePrintable {
    // 비어있음 
}
 
 
class ClassPrinter  {
    public static void print(Object obj) {
        String org = obj.toString();
        if (obj instanceof UpperCasePrintable) { 
            org = org.toUpperCase();
        }
        System.out.println(org);
    }
}
 
 
class PointOne implements UpperCasePrintable {
    private int xPos, yPos;
    PointOne(int x, int y) {
        xPos = x;
        yPos = y;
    }
    public String toString() {
 
        String posInfo= "[xpos : "+xPos+", ypos: "+yPos+"]";
        return posInfo;
    }    
}
 
class PointTwo {
    private int xPos, yPos;
    
    PointTwo(int x, int y) {
        xPos = x;
        yPos = y;
    }
    
    public String toString() {
        String posInfo= "[xpos : "+xPos+", ypos: "+yPos+"]";
        return posInfo;    
    }
}
 
 
public class interfaceMark {
 
    public static void main(String[] args) {
        PointOne pos1 = new PointOne(11);
        PointTwo pos2 = new PointTwo(22);
        PointOne pos3 = new PointOne(33);
        PointTwo pos4 = new PointTwo(44);
 
        ClassPrinter.print(pos1);
        ClassPrinter.print(pos2);
        ClassPrinter.print(pos3);
        ClassPrinter.print(pos4);
 
    }
 
}
 
cs

***13행 : instanceof 연산자 형변환의 가능/불가능을 boolean타임으로 return하는 연산자   
          if(obj instanceof UpperCasePrintable) obj가 UpperCasePrintable로 형변환 될 수 있니????

5행  : UpperCasePrintable 인터페이스가 정의되었다. 그런데 이 인터페이스 안에는 아무것도 정의되어 있지 않다. 
13행 : UpeprCasePrintable 인터페이스를 구현하는 클래스의 인스턴스인지 확인하고 잇다. 
14행 : 인스턴스 메소드 toUpperCase는 String 인스턴스에 저장된 문자열 전부를 대문자로 변경해서 반환한다. 
21행 : 클래스 PintOne은 UpperCasePrintable 인터페이스를 구현하고 있다. 물론 인터페이스가 비어있기 때문에 별도록 구현할 메소드는 존재하지 않는다. 
27행 : Object 클래스에 정의되어 있는 toString메소드가 public으로 선언되어 있기 때문에 42행 역시 public 으로 선언되어야 한다. 
34행 : 클래스 PointTwo 는 위에서 정의한 PointOne과 동일하다. 다만 UpperCasePrintable인터페이스를 구현하고 있지 않을 뿐이다. 

UpperCasePrintable 인터페이스를 구현하는 클래스의 인스턴스가 ClssPrinter.print메소드의 인자로 전달되면 대문자로 이뤄진 문자열의 출력이, 
UpperCasePrintable 인터페이스를 구현하지 않은 클래스의 인스턴스가 ClassPrinter.print 메소드의 인자로 전달되면 문자열 원본의 내용 그대로가 출력된다. 
   
위 예제에서 UpperCasePrintable 인터페이스가 갖는 의미는 무엇일까?
"다른 클래스와 구별을 위한 특별한 표시의 목적으로 사용되었다. "

■ 인터페이스를 통한 다중상속의 효과 
다중상속이란 하나의 클래스가 둘 이상의 클래스를 동시에 상속하는 것을 의미한다. 

"IPTV는 (일종의) TV다"
"IPTV는 (일종의) Computer다"

--------------다소 주관적인 이야기지만, 필자는 지금까지 다중상속을 이용해서 프로그램을 구현해 본적도 없고, 다숭상속을 개발에 적용하는 개발자를 본적이 없다. 물로 필자의 제한된 경험으로 성급한 일반화의 오류를 범할 수도 있으나, 객체지향 시스템의 개발에서 다중상속이 큰 의미를 갖지 못하는 것은 사실!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Employee
{
    public void work() { . . . } 
}
 
class Engineer extends Employee 
{
     . . . 
}
 
class Marketer extends Employee 
{
     . . . 
}
 
class TechMarketer extends Engineer, Marketer 
{
    // 이 안에서 work메소드를 호출한다면?
}
 
cs


이렇게 다이아몬드 상속이 구성되면, Employee 클래스에 정의된 메소드를  TechMarketer 클래스에서 호출하는 경우에 문제가 발생한다. ㅇ왜냐하면 TechMarketer 클래스는 Employee클래스를 간접적으로 두 번 상속(한번은 Engineer를 상속하면서, 또한 번은 Marketer를 상속)하기 때문에 호출할 메소드의 선택이 애매모호해지기 때문이다. 그래서 다중상속을 지원하는 프로그래밍 언어들은 이러한 상활에 대처하기 위한 별도의 문법을 추가해 놓고 있다. 
그러나 자바는 다중상속을 지원하지 안흥ㅁ으로 인해서 이러한 복잡한 문제를 일으키지 못하도록 제한하고 있다. 

Inner 클래스
 
■ Inner 클래스와 Nested 클래스  

1
2
3
4
5
6
7
8
class Employee
{
    . . . 
    class InnerCLass
    {
       . . . 
    }
}
cs



1
2
3
4
5
6
7
8
class OuterClass
{
    . . . 
    static class NestedClass
    {
       . . . 
    }
}
cs

Inner 클래스를 static으로 선언할 수 있는데, 이렇게 선언된 클래스를 가리져 'static inner 클래스' 또는 'nested 클래스'라도고 함 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package Inner;
 
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();
        }
    }
}    
 
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();
                
    }
            
}
 
cs


위 예제를 통해서 주목해야 할 내용 몇 가지를 정리하면 다음과 같다. 
- outer클래스의 인스턴스 생성 후에야 Inner클래스의 인스턴스 생성이 가능하다. 
- Inner클래스 내에서는 Outer클래스의 멤버에 직접 접근이 가능하다. 
- Inner클래스의 인스턴스는 자신이 속할 Outer클래스의 인스턴스를 기반으로 생성된다. 

■ Inner클래스!이거 유용하게 사용되나요?

유용하게 사용되지 않는 문법이라면 필자의 성격상 책에 억지로 구겨 넣지 않는다. 
그런데 Inner 클래스와 Nested클래스는 다음과 같은 장점을 지닌다. 
- 클래스들을 논리적으로 묶는 수단이 된다. 
- 클래스들을 논리적으로 묶다보니, 캡슐화가 증가하는 효과가 이싿. 
- 결과적으로 코드의 가독성이 향상되고, 유지보스성이 좋아진다. 

Local 클래스와 Anonymous클래스 
- Outer 클래스의 인스턴스 생성 후에야 Inner클래스의 인스턴스 생성이 가능하다. 
- Inner클래스 내에서는 Outer클래스의 멤버에 직접 접근이 가능하다. 
- Inner클래스의 인스턴스는 자신이 속할 Outer클래스의 인스턴스를 기반으로 생성된다. 

■ Local(지역) 클래스 
Local 클래스는 Inner클래스와 유사하다. 다만 메소드 내에 정의가 되고, 정의된 메소드 내에서만 인스턴스 생성과 참조변수의 선언이 가능하다는 특징이 잇다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package LocalAndAnonymous;
 
interface Readable {
    public void Read();
}
 
class OuterClass {
    
    private String myName;
    
    OuterClass(String name) {
        
        myName = name;
    }
 
 
    public Readable createLocalClassTest() {
    
        class LocalClass implements Readable {
        
            @Override
            public void Read() {
                System.out.println("Outer inst name : "+myName);
            }
        }
        return new LocalClass();
    }
}
 
class LocalClassTest {
 
    public static void main(String [] args) {
        
        OuterClass out1 = new OuterClass("First");
        Readable localInst1 = out1.createLocalClassTest();
        localInst1.Read();
 
        OuterClass out2 = new OuterClass("Second");
        Readable localInst2 = out2.createLocalClassTest();
        localInst2.Read();
    }
}
cs


-3행 : Local 클래스의 인스턴스를 메소드 외부에서 참조하기 위해 정의한 인터페이스이다. 
-17,19,26행 : LocalClass가 정의되어 있는데, 이 클래스(createLocalClassTest)가 Readable 인터페이스를 구현하고 있다는 사실에 주목. 
              때문에 15행의 반환형을Readable로 선언할 수 잇는 것이다. 
- 23행 : Inner클래스와 마찬가지로 Outer클래스의 멤버에 접근이 가능함을 보이고 있다. 
- 35, 36행 : Readable의 참조변수로 메소드 createLocalClassInst 내에서 생성된 인스턴스에 접근하고 있다. 

위 예제에서 보듯이 Local 클래스가 정의되면, Local클래스의 인스턴스 접근을 위해서 인터페이스가 함께 정의도는 것이 보통이다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package LocalAndAnonymous2;
 
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("Outer inst ID : "+instID);
            }
        }
        return new LocalClass();
    }
}
 
public class LocalParamClassTest {
    
    public static void main(String []args) {
        OuterClass out = new OuterClass("My OuterClass");
        Readable localInst1 = out.createLocalClassinst(111);
        localInst1.read();
        
        Readable localInst2 = out.createLocalClassinst(222);
        localInst2.read();
    }
}
cs


Local 클래스는 final로 선언된 지역변수 또는 매개변수에 접근이 가능하다. 왜??
"왜 final로 선언된 변수에만 접근이 가능하지? "
필자가 궁금한 것 
 "매개변수와 지역변수는 메소드를 빠져나가면 소멸이 되는데, 어떻게 접근 가능할까?"

32행에서 11을 전달하면서 createLocalClassInst메소드를 호출하고 있다.
참조변수 lcoalsInst는 이대 생성된 인스턴스를 참조하게 된다. 즉, createLocalClassinst메소드가 종료되면서 
일어나는 일이다. 
111로 초기화된 매개변수 instID는 익 과정에서 소멸된다. 

이 문제점 때문에 컴파일러는 Local 클래스에 접근하는 지역변수와 매개변수의 복사본을, Local 클래스가 항상 접근 가능한 메모리 영역에 만들어 둔다. 
때문에 컴파일러는 이들 변수를 final로 선언할 것을 강요하는 것이다. 
만약 final이 아니라면 Local클래스내에서 이 값을 변경시킨다면, 원본과 복사본읙 값이 일치하지 않는 문제가 발생한다. 

 
■ Anonymous 클래스 
Anonymous는 Local 클래스와 유사하다.(따라서 inner클래스와도 유사하다.) 단, Local 클래스는 이름이 있지만, Anonymous 클래슨 이름이 없다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package LocalAndAnonymous3;
 
interface Readable {
    public void read();
}
 
class OuterClass {
    private String myName;
    OuterClass(String name) {
        myName = name;
    }
    
    public Readable createLocalClassInst(final int instID) {
        
        return new Readable() {
            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) {
        OuterClass out = new OuterClass("My OuterClass");
        Readable localInst1 = out.createLocalClassInst(111);
        localInst1.read();
        
        Readable localInst2 = out.createLocalClassInst(222);
        localInst2.read();
    }
}
 
cs


위 예제에서 보이듯이 실행 결과는 LocalParamClassTest.java과 완전히 동일하다. 사실 위의 두 예제는 동일한 예제일 뿐이다. 
다만 Local 클래스를 활용했느냐, Anonymous클래스를 활용했느냐에 따른 차이만 있을 뿐이다. 
   ++ Anonymous 클래스의 생성자 
      Anonymous 클래스는 인터페이스의 메소드를 완성하는 방식으로로 정의되기 때문에, 생성자가 필요한 상황에서는 어울리지 않는다.
      그리고 실제로 이름이 없기 때문에 생성자를 정의하고 싶어도 정의할 수없다. 


반응형
LIST