1. 내부 클래스 (Inner class)


# 클래스 안에 또 다른 클래스를 넣는 것을 의미한다. 

* 외부 클래스의 요소라고 생각하면 이해하기 쉽다. 따라서 접근 제어자가 붙을 수 있다. 

* 내부 클래스, 이너 클래스, 중첩 클래스라고도 불린다. 


* 밖에 위치한 클래스를 외부 클래스라고 하며, 안에 위치한 클래스를 내부 클래스라고 한다. 


1) 내부 클래스를 사용하는 이유 

- 코드의 간략화하기 위해서이다. 

즉, 내부 클래스의 코드가 매우 간단하거나 내부 클래스가 외부 클래스에 의해서 독점적으로 사용된다면 두 클래스는 내부 클래스 형태로 합치는 것이다. 

그러나 내부 클래스의 코드가 복잡해지거나 다른 클래스에 의해서 사용될 상황이 생긴다면 내부 클래스를 다른 Java 소스 파일로 생성하는 것을 추천한다.


- 외부 클래스의 이름을 파일의 이름으로 저장하기 때문에,

하나의 자바 파일로 2개의 클래스를 사용할 수 있기 때문에 코드 관리에 편리하다. 

또한 하나의 외부 클래스 내부에 여러 개의 내부 클래스를 선언할 수도 있다. 


2) 내부 클래스의 종류 

(1) instance 내부 클래스 (인스턴스 내부 클래스)

- 가장 일반적인 형태로 클래스 내부에 클래스를 선언한 것


(2) static 내부 클래스 (정적 내부 클래스)

- static 키워드가 사용된 내부 클래스 


(3) local 내부 클래스 (지역 내부 클래스)

- 메소드 내부에 클래스를 선언한 것으로 메소드 내부에서만 유효한 것

(4) anonymous 클래스 (익명 내부 클래스)

- 이미 만들어진 클래스를 필요한 메소드만 재정의해서 사용하는 것 (일회용)



2. instance inner class (인스턴스 내부 클래스)


# 외부 클래스 안에 새로운 클래스를 선언하는 것

* 가장 일반적인 형태이기 때문에, 보통 이를 그냥 내부 클래스라고 부르기도 한다.


1) 구조 


2) 특징 

(1) 내부 클래스의 객체를 생성하기 위해서는 반드시 외부 클래스의 객체가 필요하다. 

외부 클래스 객체를 생성한 뒤 객체로부터 내부 클래스를 인스턴스화 해야 한다. 


(2) 내부 클래스는 일반 클래스처럼 생성자를 포함한 변수와 메소드를 선언하여 사용할 수 있다.


3) 주의사항

(1) 내부 클래스의 메소드나 속성에 static 키워드 선언을 할 수 없다. 

(2) 단, final 키워드와 붙여서 선언할 수는 있다.

(3) 내부 클래스를 사용하기 위해서는 외부 클래스를 인스턴스화 해야 한다.

(4) 내부 클래스의 클래스 이름은 외부 클래스 안에서만 중복되지 않으면 된다. 


# instance inner class 연습하기 (1)

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
package innerex;
 
// 1) instance inner class
class Outer {
    private int outerHashCode;
    public String outerClassName;
    static boolean isOuter;
    
    public Outer() {
        outerHashCode = System.identityHashCode(this);
        outerClassName = this.getClass().getName();
        isOuter = true;
    }//constructor
    
    class InnerClass {
        private int innerHashCode;
        public String innerClassName;
        // final 키워드 단독으로 선언할 수 없다. 
        static final boolean isInner = true;
        
        public InnerClass() {
            innerHashCode = System.identityHashCode(this);
            innerClassName = this.getClass().getName();
        }
        
        // 내부 클래스는 여러가지 접근 제어자로 선언된 외부 클래스 변수에 접근할 수 있다. 
        public void printOuterInfo() {
            System.out.println("Outer object hashcode: " + outerHashCode);
            System.out.println("Outer object class name: " + outerClassName);
            System.out.println("Is it outer class: " + isOuter);
        }
        
        // 내부 클래스 자신의 변수에도 접근할 수 있다. 
        public void printInnerInfo() {
            System.out.println("Inner object hashcode: " + innerHashCode);
            System.out.println("Inner object class name: " + innerClassName);
            System.out.println("Is it inner class: " + isInner);
        }
    }//InnerClass
}//OuterClass
 
public class InnerEx01 {
    public static void main(String[] args) {
 
        // 외부 클래스 객체 outer 생성
        Outer outer = new Outer();
        
        // outer 객체를 사용하여 내부 클래스 객체 inner 생성  
        Outer.InnerClass inner = outer.new InnerClass();
        Outer.InnerClass inner2 = new Outer().new InnerClass();
 
        inner.printInnerInfo();
        System.out.println();
        inner.printOuterInfo();
        
    }
}
cs

# 실행 결과

Inner object hashcode: 515132998

Inner object class name: innerex.Outer$InnerClass

Is it inner class: true


Outer object hashcode: 1973538135

Outer object class name: innerex.Outer

Is it outer class: true


# 설명

- 외부 클래스 Outer 안의 인스턴스 내부 클래스 InnerClass

Line 5~6: 외부 클래스 Outer의 변수 선언 

Line 9~13: 외부 클래스 Outer의 생성자를 통한 변수 초기화 

Line 15~39: 인스턴스 내부 클래스 InnerClass 부분 

Line 16~19: 내부 클래스의 변수 선언 

Line 21~24: 내부 클래스의 생성자를 통한 변수 초기화 

Line 27~31: 외부 클래스의 변수에 접근하여 처리하는 메서드

내부 클래스는 여러가지 접근 제어자로 선언된 외부 클래스 변수에 접근할 수 있다.  

Line 34~38: 자신인 내부 클래스의 변수에 접근하여 처리하는 메서드 


# instance inner class 연습하기 (2)

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
package innerex;
 
// 1) instance inner class
class Outer2 {
    int id = 10;
    
    class Inner {
        int val1 = 1;
        int val2 = 2;
        int val3 = 3;
        
        void m() {}
        void n() {}
    }//InnerClass
}//OuterClass
 
public class InnerTest02 {
    public static void main(String[] args) {
        
        Outer2 outer = new Outer2();
        System.out.println("outer.id: " + outer.id);
        
        // Outer2.Inner inner = outer.new Inner(); <- 가능 
        Outer2.Inner inner = new Outer2().new Inner();
        System.out.println("inner.val1: " + inner.val1);
        
    }
}
cs

# 실행 결과

outer.id: 10

inner.val1: 1


# instance inner class 연습하기 (3)

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
package innerex;
 
import java.util.Date;
 
// 1) instance inner class
class MyCalendar {
    
    Date date = new Date();
    
    class Diary {
        // 인스턴스 내부 클래스에는 static 키워드가 붙은 속성이나 메소드가 올 수 없다. 
        // static int n = 10; 
        static final int NUM = 1
        
        void record() {
            System.out.println(date + "에 다이어리 구매");
        }
    }//InnerClass(Diary)
    
    class Household_ledger {
        void buy() {
            System.out.println(date + "에 가계부 구매");
        }
    }//InnerClass(Household_ledger)
    
}//OuterClass(MyCalendar)
 
public class InnerTest03 {
    public static void main(String[] args) {
 
        MyCalendar cal = new MyCalendar();
        MyCalendar.Diary diary = cal.new Diary();
        MyCalendar.Household_ledger hl = cal.new Household_ledger();
        
        System.out.println("diary.NUM: " + diary.NUM);
        diary.record();
        hl.buy();
        
    }
}
cs

# 실행 결과

diary.NUM: 1

Tue Jan 30 19:58:22 KST 2018에 다이어리 구매

Tue Jan 30 19:58:22 KST 2018에 가계부 구매




3. static inner class (정적 내부 클래스)


# 내부 클래스의 선언부에 static 키워드를 붙인 내부 클래스 


1) 구조

- static 키워드가 선언부에 같이 사용되므로 인스턴스화 하지 않아도 사용할 수 있음 


2) 특징

(1) 앞서 static 키워드는 클래스 변수와 메소드 선언부에만 사용될 수 있다고 하였다. 

하지만 내부 클래스는 외부 클래스의 속성과 같이 취급되므로 static 키워드를 사용해도 된다. 


(2) (1)과 같은 이유로 내부 클래스 앞에 private 키워드를 붙여 캡슐화(은닉화) 개념을 적용할 수도 있다! 

(3) static 키워드가 선언부에 같이 사용되므로 인스턴스화 하지 않아도 사용할 수 있다.


# static inner class 연습하기 (1)

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
package innerex;
 
// 2) static inner class
class Outer2 {
    static class Inner {
        static String keyword = "STATIC INNER CLASS";
        
        public void printInfo() {
            System.out.println("You called printInfo method");
        }
        
        public static void printName() {
            System.out.println("You called printName method");
        }
    }//InnerClass
}//OuterClass
 
 
public class InnerEx02 {
    public static void main(String[] args) {
 
        Outer2.Inner inner = new Outer2.Inner();
        
        System.out.println("keyword: " + inner.keyword);
        inner.printInfo();
        Outer2.Inner.printName();
        
    }
}
cs

# 실행 결과

keyword: STATIC INNER CLASS

You called printInfo method

You called printName method


# 설명

- 외부 클래스 Outer2 안의 정적 내부 클래스 Inner

Line 5~15: 정적 내부 클래스 Inner 부분 

Line 8~10: 인스턴스 메소드이기 때문에 인스턴스화를 거친 객체를 통해서만 호출이 가능하다. > Line 25

Line 12~14: 클래스 메소드이기 때문에 인스턴스화를 거치지 않고도 호출이 가능하다. > Line 26 


# static inner class 연습하기 (2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package innerex;
 
// 2) static inner class
class Outer {
    
    static String strOuter = "Outer";
    
    static class Inner {
        static String strInner = "Inner";
    }//InnerClass
    
}//OuterClass
 
public class InnerTest01 {
    public static void main(String[] args) {
        
        Outer.Inner inner = new Outer.Inner();
        System.out.println("Outer.num: " + Outer.strOuter);
        System.out.println("Outer.Inner.x: " + Outer.Inner.strInner);
        
    }
}
cs

# 실행 결과

Outer.num: Outer

Outer.Inner.x: Inner



4. local inner class (지역 내부 클래스)


# 메소드 내부에 클래스를 선언해서 사용하는 내부 클래스 


1) 특징

(1) 지역 내부 클래스는 메소드 내부에서만 유효하다. 

때문에 다른 메소드에서 지역 내부 클래스 객체를 생성할 수 없다.


# local inner class 연습하기 (1)

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
package innerex;
 
import java.util.Date;
 
// 3) local inner class
class Outer3 {
    
    public void printStatus() {
        class DateFormat {
            private Date date;
            
            // 지역 내부 클래스 DateFormat 생성자 
            public DateFormat(Date date) {
                this.date = date;
            }
            
            // 지역 내부 클래스 DateFormat 메소드 
            public String getDateFormat() {
                return date.toString();
            }
        }//InnerClass
        
        DateFormat format = new DateFormat(new Date());
        System.out.println("The Date: " + format.getDateFormat());
    }//printStatus()
    
}//OuterClass
 
 
public class InnerEx03 {
    public static void main(String[] args) {
 
        Outer3 outer = new Outer3();
        outer.printStatus();
        
    }
}
cs

# 실행 결과

The Date: Tue Jan 30 18:48:24 KST 2018


# 설명 

- 외부 클래스 Outer의 메소드인 printStatus() 안의 지역 내부 클래스 DateFormat

Line 9~21: 지역 내부 클래스 부분 

Line 33~34: 지역 내부 클래스는 소속되어 있는 메소드 안에서만 유효하다. 

때문에 사용하기 위해서는 인스턴스화를 통하여 접근해야 한다. 


# local inner class 연습하기 (2)

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 innerex;
 
// 3) local inner class
class OutPrint {
    
    void print(int choice) {
        
        class InPrint {
            void hpPrinter() {
                System.out.println("hp로 인쇄");
            }
            void epsonPrinter() {
                System.out.println("epson로 인쇄");
            }
            void samsungPrinter() {
                System.out.println("samsung로 인쇄");
            }
        }//LocalInnerClass
 
        InPrint ip = new InPrint();
        if(choice == 1) {
            ip.hpPrinter();
        }else if(choice == 2) {
            ip.epsonPrinter();
        }else if(choice == 3) {
            ip.samsungPrinter();
        }
    }//Print()
    
}//OuterClass
 
public class InnerTest04 {
    public static void main(String[] args) {
 
        new OutPrint().print(2);
        
    }
}

cs

# 실행 결과

epson로 인쇄



5. anonymous inner class (익명 내부 클래스)


# 일반적인 클래스 선언과 달리 선언부 없이 실행부만으로 사용되는 내부 클래스

* 보통 일회용으로 사용된다. 


1) 특징

(1) 클래스의 선언부가 없으므로 클래스의 이름 또한 존재하지 않으며,

다른 클래스와 관계를 맺을 수 없고, 보통 인스턴스화되어 일회용으로 사용한다. 


(2) 지역 내부 클래스를 선언하고 나면 마지막에 세미콜론(;)을 반드시 붙여야한다. 


2) 익명 내부 클래스를 사용하는 경우

(1) 인터페이스를 구현한 클래스가 잠시 필요한 경우

(2) 기존에 구현된 클래스의 일부만 오버라이딩해서 변경하는 경우


# anonymous inner class 연습하기 (1)

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
package innerex;
 
import java.util.Date;
 
// 4) anonymous inner class
class Outer4 {
    public Object getName() {
        return new Object() {
            @Override
            public String toString() {
                return this.getClass().getName();
            }
        }; //<- 세미콜론 
        //InnerClass
    }
    
}//OuterClass
 
public class InnerEx04 {
    public static void main(String[] args) {
 
        Outer4 outer = new Outer4();
        System.out.println(outer.getName().toString());
        
    }
}
cs

# 실행 결과

innerex.Outer4$1


# 설명

Line 9~13: 익명 내부 클래스 부분

Line 10: toString() 메소드를 오버라이딩하여 재정의


# anonymous inner class 연습하기 (2)

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 innerex;
 
// 4) anonymous inner class
abstract class Car {
    abstract void run();
}
 
class Bus extends Car {
    @Override
    void run() {
        System.out.println("BUS");
    }
}
 
class Taxi extends Car {
    @Override
    void run() {
        System.out.println("TAXI");
    }
}
 
public class InnerTest05 {
    public static void main(String[] args) {
 
        // Car를 상속받는 클래스들이 여러개일때, 
        // 각각 run()메소드를 오버라이딩하는 것이 비효율적이다.
        // 때문에 익명 내부 클래스를 사용하는 것이다. 
        
        Car c = new Car() {
            
            @Override
            void run() {
                System.out.println("부모 클래스에서 재정의");
            }
        }; //InnerClass
        
        c.run();
        new Bus().run();
        new Taxi().run();
        
    }
}
cs

# 실행 결과

부모 클래스에서 재정의

BUS

TAXI



# anonymous inner class 연습하기 (3)

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
package innerex;
 
import java.awt.Frame;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
 
// 4) anonymous inner class
class MyListener implements WindowListener {
 
    // WindowClosing 메소드만 사용하고 싶은데, 
    // 7개의 모든 메소드를 오버라이딩해야 할 때 익명 내부 클래스를 사용한다. 
    
    @Override
    public void windowOpened(WindowEvent e) {}
 
    @Override
    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }
 
    @Override
    public void windowClosed(WindowEvent e) {}
 
    @Override
    public void windowIconified(WindowEvent e) {}
 
    @Override
    public void windowDeiconified(WindowEvent e) {}
 
    @Override
    public void windowActivated(WindowEvent e) {}
 
    @Override
    public void windowDeactivated(WindowEvent e) {}
}
 
public class InnerTest06 {
    public static void main(String[] args) {
 
        Frame fr = new Frame();
        fr.setSize(100100);
        fr.setVisible(true);
        
        //fr.addWindowListener(new MyListener());
        fr.addWindowListener(new WindowAdapter() {
            // 다른 메소드들은 재정의하지 않고 필요한 것만 재정의해서 사용 
            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
    }
}
cs


# anonymous inner class 연습하기 (4)

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 innerex;
 
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Panel;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
 
public class InnerTest07 extends Frame implements MouseListener {
    
    Panel p1, p2, p3, p4;
    CardLayout card; // 슬라이드 쇼 -> 카드 레이아웃
    
    public InnerTest07() {
        super("Mouse event");
        card = new CardLayout();
        setLayout(card);
        setBounds(200200300300);
        
        p1 = new Panel();
        p2 = new Panel();
        p3 = new Panel();
        p4 = new Panel();
        
        p1.setBackground(Color.CYAN);
        p2.setBackground(Color.RED);
        p3.setBackground(Color.GREEN);
        p4.setBackground(Color.BLUE);
        
        p1.addMouseListener(this);
        p2.addMouseListener(this);
        p3.addMouseListener(this);
        p4.addMouseListener(this);
        
        add(p1);
        add(p2);
        add(p3);
        add(p4);
    }
    
    @Override
    public void mouseClicked(MouseEvent e) {
        card.next(this);
    }
 
    @Override
    public void mousePressed(MouseEvent e) {}
 
    @Override
    public void mouseReleased(MouseEvent e) {}
 
    @Override
    public void mouseEntered(MouseEvent e) {}
 
    @Override
    public void mouseExited(MouseEvent e) {}
    
    public static void main(String[] args) {
 
        InnerTest07 i7 = new InnerTest07();
        i7.setVisible(true);
        
    }
}
cs



6. 내부 클래스의 바이너리 파일 생성


- 파일을 컴파일하면 내부 클래스와 외부 클래스의 개수만큼 바이너리 파일이 생성된다.

같은 java 파일을 사용하고 있더라도 내부 클래스 또한 클래스이므로 바이너리 파일이 생성되기 때문이다. 





7. 내부 클래스의 필요성


- 내부 클래스는 코드의 간략화를 위해 사용되지만 오히려 코드를 찾기 힘들게 만들 수 있다 .

- 또 다른 주된 이유는 '클래스는 언제든지 그 기능이 확장될 수 있고 수정될 수 있어야 하기 때문'이다. 

내부 클래스로 선언되어 있으면 기능의 확장이나 수정이 불편해지는 부분이 발생한다.


'JAVA > JAVA2' 카테고리의 다른 글

JAVA2_day13 | GUI 연습문제 (2)  (0) 2018.01.30
JAVA2_day13 | GUI (PopupMenu, MenuItem)  (0) 2018.01.30
JAVA2_day11 | GUI 연습문제 (1)  (0) 2018.01.30
JAVA2_day11 | GUI (LayoutManager)  (0) 2018.01.30
JAVA2_day11 | GUI (List, Choice)  (0) 2018.01.30