네스티드 클래스와 이너 클래스

gustjtmd·2022년 1월 12일
0

Java

목록 보기
29/40
다음과 같이 클래스 안에 또 다른 클래스를 정의할 수 있다.
class Outer{
	class Nested{...}	//네스티드 클래스
}
이렇게 클래스 내에 정의된 클래스를 가리켜 '네스티드 클래스'라 하고 이를 감싸는 클래스를 가리켜 '외부 클래스'라 한다.

네스티드 클래스의 구분

기본적으로 클래스 내에 정의되는 모든 클래스를 가리켜 '네스티드 클래스'라 하는데, 네스티드 클래스는 static의 선언 여부를 기준으로 다음과 같이 나뉜다.
  • static 네스티드 클래스
  • 이너 클래스 (Non- static 네스티드 클래스)
class Outer Class{
	static class StaticNestedClass{...} // static 네스티드 클래스
}
class OuterClass{
	class InnerClass{...}	//이너 클래스(Non-static 클래스)
}
그리고 이너 클래스는 정의되는 위치나 특성에 따라 다시 세 종류로 나뉜다
  • 멤버 클래스(Member Inner Class)
  • 로컬 클래스(Local Inner Class)
  • 익명 클래스(Anonymous Inner Class)

static 네스티드 클래스

Static 네스티드 클래스는 static 선언이 갖는 특성이 반영된 클래스이다. 따라서 자신을
감싸는 외부 클래스의 인스턴스와 상관없이 Static 네스티드 클래스의 인스턴스 생성이 가능.
-------------------------------------------------------------------
class Outer{
    private static int num = 0;
    static class Nested1{   //Static 네스티드 클래스
        void add(int n){
            num += n;
        }
    }
    static class Nested2{   //Static 네스티드 클래스
        int get(){
            return num;
        }
    }
}
public class StaticNested {
    public static void main(String[] args) {
        Outer.Nested1 nst1 = new Outer.Nested1();
        nst1.add(5);
        Outer.Nested2 nst2 = new Outer.Nested2();
        System.out.println(nst2.get());
    }
}
----------------------------------------------------------------------
5

class Outer{
    private static int num = 0;
static class Nested1{   //Static 네스티드 클래스
        void add(int n){num += n;}
    }
    static class Nested2{   //Static 네스티드 클래스
        int get(){return num;}
    }
}
위의 Nested1, Nested2 클래스 내에서는 Outerstatic 멤버 num에 접근하고 있다.
private으로 선언 되어 있어도 접근이 가능하다. 따라서 Outerstatic 멤버 num은
Nested1Nested2의 모든 인스턴스가 공유하게 된다. 그리고 이것이
'Static 네스티드 클래스'가 갖는 주요 특징이다.
-------------------------------------------------------------------

예제에서 본듯이 Static 네스티드 클래스의 인스턴스 생성은 외부 클래스(Outer 클래스)
인스턴스 생성과 무관하다
'(외부 클래스 인스턴스 생성 않고도 Static 네스티드 클래스 인스턴스 생성 가능)'

'Static 네스티드 클래스 내에서 외부 클래스의 인스턴스 변수와 메소드에 접근 불가능하다'Static 네스티드 클래스 내에서는 외부 클래스에 static으로 선언된 변수와
메소드에만 접근이 가능하다.

이너 클래스의 구분

  • 멤버 클래스(Member Inner Class)
    -> 인스턴스 변수, 인스턴스 메소드와 동일한 위치에 정의.
  • 로컬 클래스(Local Inner Class)
    -> 중괄호 내에 특히 메소드 내에 정의.
  • 익명 클래스(Anonymous Inner Class)
    -> 클래스 이름이 존재하지 않는, 이름 그대로 익명의 클래스이다. 따라서 위의 두 클래스와 혼동할 일이 없는 클래스
class Outer{
	class MemberInner{...}	//멤버 클래스
    
    void method(){
    	class LocalInner{...}	//로컬 클래스
    }
}

멤버 클래스

class Outer{
    private int num = 0;
    class Member{  //멤버 클래스의 정의
        void add(int n){
            num += n;
        }
        int get(){
            return num;
        }
    }
}
public class MemberInner {
    public static void main(String[] args) {
        Outer o1 = new Outer();
        Outer o2 = new Outer();

        //o1 기반으로 두 인스턴스 생성
        Outer.Member o1m1 = o1.new Member();
        Outer.Member o1m2 = o1.new Member();

        //o2 기반으로 두 인스턴스 생성
        Outer.Member o2m1 = o2.new Member();
        Outer.Member o2m2 = o2.new Member();

        // o1기반으로 생성된 두 인스턴스의 메소드 호출
        o1m1.add(5);
        System.out.println(o1m2.get());

        // o2기반으로 생성된 두 인스턴스의 메소드 호출
        o2m1.add(7);
        System.out.println(o2m2.get());
    }
}
--------------------------------------------------------------------
5
7
위 예제에서 정의하는 '멤버 클래스'는 다음과 같다
class Outer{
    private int num = 0;
    class Member{  //멤버 클래스의 정의
        void add(int n){num += n;}
        int get(){return num;}
    }
}
------------------------------------------------------------------
Member 클래스 내에서는 Outer 클래스의 인스턴스 변수에 접근이 가능하다.
인스턴스 변수가 private으로 선언 되어 있어도 가능하다. 그리고 이러한 멤버 클래스의
가장 큰 특징은 다음과 같다
'멤버 클래스의 인스턴스는 외부 클래스의 인스턴스에 종속적이다.'

이 문장이 말바는 바를 이해하기 위해 멤버 클래스의 인스턴스 생성과정을 보자
Outer o1 = new Outer();

이를 기반으로 다음과 같이 Member 인스턴스를 생성하였다
(Outer 인스턴스 없이 Member 인스턴스 생성 불가)

Outer.Member o1m1 = o1.new Member();
Outer.Member m1m2 = o1.new Member();
그러면 위의 두 인스턴스는 o1이 참조하는 인스턴스의 멤버에 접근할 수 있다.
즉 위의 두 인스턴스는 o1이 참조하는 인스턴스의 멤버를 공유하게 된다. 실행결과로 확인 가능함.

멤버 클래스를 언제 사용하는가?

"클래스의 정의를 감추어야 할 때 유용하게 사용이 된다.
interface Printable{
    void print();
}
class Papers{
    private String con;
    public Papers(String s){
        con = s;
    }
    public Printable getPrinter(){
        return new Printer(); //멤버 클래스 인스턴스 생성 및 반환
    }
    private class Printer implements Printable{   //멤버 클래스의 정의
        public void print(){
            System.out.println(con);
        }
    }
}
public class UseMemberBerInner {
    public static void main(String[] args) {
        Papers p = new Papers("서류 내용 : 행복합니다.");
        Printable prn = p.getPrinter();
        prn.print();
    }
}
-------------------------------------------------------------------------
서류 내용 : 행복합니다.


위의 예제에서
interface Printable{
    void print();
} 

이 인터페이스를 구현하는 Printer 클래스를 다음과 같이 Papers 클래스 내에 정의하였다.
class Papers{
	...
    public Printable getPrinter(){
    	return new Printer();	//멤버 클래스 인스턴스 생성 및 반환
    }
    private class Implements Printable{ //멤버 클래스의 정의
    	public void print(){
        	System.out.println(con);
        }
    }
}

Printer 클래스는 private으로 선언되었다. 이렇듯 멤버 클래스가 private으로 선언되면
이 클래스 정의를 감싸는 클래스 내에서만 인스턴스 생성이 가능하다. 
때문에 이 Printer 다음과 같은 방법으로만 참조가 가능하다.

	public static void main(String[] args){
	Papers p = new Papers("서류 내용 : 행복합니다.");
    	Printable prn = p.getPrinter();
}

-----------------------------------------------------------------------

이제 Papers클래스의 외부에서는 getPrinter 메소드가 어떠한 인스턴스의 참조값을 반환하는지
알지 못한다. 다만 반환되는 참조 값의 인스턴스가 Printable을 구현하고 있어서 
Printable의 참조 변수로 참조할 수 있다는 사실만 알뿐이다.
그리고 이러한 상황을'클래스의 정의가 감추어진 상황'이라 한다.
이렇게 클래스의 정의를 감추면 getPrinter 메소드가 반환하는 인스턴스가 다른 클래스의
인스턴스로 변경되어도 Papers 클래스 외부의 코드는 조금도 수정할 필요가 없게된다
(즉 코드에 유연성이 부여되었다. 그리고 이는 엄청난 장점)

반복자는 멤버 클래스이다.

public static void main(String [] args){
	List<String> list = new ArrayList<>();
    	Iterator<String> itr = list.iterator();	//반복자 획득
}

public class ArrayList<E> implements List<E>{
	......
    public Iterator<E> iterator(){
    	return new Itr();	//멤버 클래스의 인스턴스 생성 및 반환
    }
    
    private class Itr implements Iterator<E>{	//멤버 클래스의 정의
    	...
    }
}
-----------------------------------------------------------------------
위의 iterator 메소드는 실제 자바의 iterator 메소드와 동이랗다.
그리고 ArrayList<E> 인스턴스가 반환하는 반복자는 Itr클래스의 인스턴스임을 알수있다
그러나 이러한 정보는 우리에게 의미가 없다.
Itr 클래스는 그 정의가 감춰져 있으므로 이 클래스의 수정 및 변경은 우리가 작성하는
코드에 아무런 영향을 미치지 않는다는 사실이 중요하다.

로컬 클래스

'로컬 클래스'는 정의위치가 메소드 몸체와 같은 블록 안에 정의 된다는 점에서 '멤버 클래스'와 구분될 뿐이다.
멤버 클래스보다도 클래스를 더 깊이 특정 블록 안으로 감추는 효과가 있다.
interface Printable{
   void print();
}

class Papers{
   private String con;
   public Papers(String s){con = s;}

   public Printable getPrinter(){
       class Printer implements Printable{ //로컬 클래스의 정의
           public void print(){
               System.out.println(con);	
           }
       }
       return new Printer(); //로컬 클래스의 인스턴스 생성 및 반환
   }
}
public class UseLocalInner {
   public static void main(String[] args) {
       Papers p = new Papers("서류 내용 : 행복합니다.");
       Printable prn = p.getPrinter();
       prn.print();
   }
}
-------------------------------------------------------------------
서류 내용 : 행복합니다.

--------------------------------------------------------------------
멤버 클래스와의 차이점은 메소드 안에 클래스 Printer의 정의가 위치한다는것.
public Printable getPrinter(){
       class Printer implements Printable{
           public void print(){
               System.out.println(con);	
           }
       }
       return new Printer();
--------------------------------------------------------------------
이렇듯 메소드 내에 클래스를 정의하면 해당 메소드 내에서만 인스턴스 생성이 가능하다
(따라서 클래스에 대한 private 선언은 의미가 없다 어차피 메소드 내에서만 인스턴스 생성이
가능하기 때문)
즉 멤버 클래스보다도 클래스를 더 깊이 특정 블록 안으로 감추는 효과가 있다.

익명 클래스(Anonymous Class)

이 메소드 내에 클래스 Printer의 정의와 printer 인스턴스 생성이 분리되어 있다.
public Printable getPrinter(){
        class Printer implements Printable{	//Printer의 정의
            public void print(){
                System.out.println(con);	
            }
        }
        return new Printer();	//Printer 인스턴스의 생성
        
'익명 클래스'의 형태로 정의하면 클래스의 정의와 인스턴스 생성을 하나로 묶을수 있음.
public Printable getPrinter(){
	return new Printable(){
    	   public void print()
        	System.out.println(con);
    }
}
위의 코드를 이해하기 위해 먼저 다음 문장을 보자. 이 문장에서는 인터페이스 Printable을
대상으로 인스턴스를 생성하고 있으니 문제가 있는 문장이다.
new Printable();

그런데 다음과 같이 해당 인터페이스를 구현하는 클래스의 정의(클래스의 몸체)를 덧붙이면
인스턴스 생성이 가능하다. 
약속이라고 생각해두자!
new Printable(){
	public void print(){
    	System,out,println(con)
    }
};

익명클래스 더 활용해보기.

class StrComp implements Comparator<String> {
    @Override
    public int compare(String s1, String s2){
        return s1.length()-s2.length();
    }
}
public class SortComparator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("ROBOT");
        list.add("APPLE");
        list.add("BOX");

        StrComp cmp = new StrComp();    //정렬기준
        Collections.sort(list,cmp);     //저열ㄹ 기준 변경해서 정렬 진행
        System.out.println(list);
    }
}
--------------------------------------------------------------------
[BOX, ROBOT, APPLE]

---------------------------------------------------------------------
위의 예제를 익명 클래스 기반으로 수정하면 다음과 같다. 그리고 이것이 자바에 람다가 등장하기
이전에 주로 사용하던 코드 스타일이다.

List<String> list = new ArrayList<>();
        list.add("ROBOT");
        list.add("APPLE");
        list.add("BOX");

        Comparator<String> cmp = new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length()-o2.length();
            }
        };
        Collections.sort(list,cmp);
        System.out.println(list);
------------------------------------------------------------------------
[BOX, ROBOT, APPLE]
profile
반갑습니다

0개의 댓글