자바/자바 입문 공부일지

자바 기초 공부 일지 45. 네스티드 클래스, 스테틱 클래스, 이너 클래스

Tomitom 2022. 11. 3. 16:34
반응형

 

 

네스티드Nested ( nest, 둥지를 틀다. 새 둥지) 는 중첩을 의미합니다. 

클래스 안에 들어있는 클래스를 네스티드 클래스라고 합니다. 

 

 

네스티드 클래스 안에는 Static 클래스와 Non-Static 클래스가 있습니다.

Static  클래스의 경우에는 정적 클래스 

Static 클래스가 아닐 경우에는 Non-Static 네스티드 클래스 또는 이너 클래스라고 합니다. 

이너 클래스는 다시 멤버 클래스, 로컬 클래스, 익명 클래스로 나뉘어집니다. 

 

- 멤버 클래스는 클래스 안에서 멤버 역할을 하는 클래스

- 로컬 클래스는 메소드 안에 만들어진 클래스 (메소드 안의 영역을 로컬이라고 합니다.) 

- 익명 클래스는 클래스를 일회성으로 재정의해서 사용하는 클래스 

 

● Static 네스티드 클래스 

Static 네스티드 클래스는 static 선언이 갖는 특성이 반영된 클래스입니다.

따라서 자신을 감싸는 외부 클래스의 인스턴스와 상관없이 Static 네스티드 클래스의 인스턴스 생성이 가능합니다. 

package day19;


/*
 * 클래스 멤버는 인스턴스 멤버에 접근할 수 없다. 
 * 클래스 멤버에서는 인스턴스 멤버의 존재 여부를 보장할 수 없기 때문이다. 
 */
class Outer{
	
	static int num = 10; // 스테틱 멤버는 인스턴스 멤버에 대한 직접적 접근 불가, 그 반대는 가능. 
	
	static class INum { 	//아우터 안의 스테틱 클래스. 정적인 클래스이기 때문에 아우터의 인스턴스가 없어도 바로 사용할 수 있습니다. 아이넘에 접근 가능
		void showNum() {
			System.out.println(num);
		}
	 }
}

public class Nested01 {
	public static void main(String[] args) {
		
		Outer.INum obj = new Outer.INum();
		obj.showNum();
	}
}

 

 

package day19;

/
class Outer{
   
    static int num = 10; // 스테틱 멤버는 인스턴스 멤버에 대한 직접적 접근 불가, 그 반대는 가능. 여기에서 static을 지우면 오류. 
   
    static class INum {     //아우터 안의 스테틱 클래스. 정적인 클래스이기 때문에 아우터의 인스턴스가 없어도 바로 사용할 수 있습니다. 아이넘에 접근 가능
 
        void showNum() {
            System.out.println(num);
        }
     }
}

public class Nested01 {
    public static void main(String[] args) {
       
        Outer.INum obj = new Outer.INum();
        obj.showNum();
    }
}

클래스 멤버는 인스턴스 멤버의 존재 여부를 보장할 수 없기 때문에 인스턴스 멤버에 접근할 수 없습니다. 

Static 네스티드 클래스는 외부 클래스의 메소드에도 접근할 수 없습니다. 

 

 


● 이너 클래스의 구성에 대해서 알아볼게요. 

 

 

○ 멤버 클래스 (Member Class)

  → 인스턴스 변수, 인스턴스 메소드와 동일한 위치에 정의

 

class Outer {

   class MemberInner {...}   // 멤버 클래스

 

 로컬 클래스 (Local Class)

  → 중괄호 내에, 특히 메소드 내에 정의

 

  void method() {

      class LocalInner {...}   // 로컬 클래스

   }

}

 

익명 클래스 (Anonymous Class)

  → 클래스인데 이름이 없음.

 


각각의 클래스를 살펴볼게요.

 

(1) 멤버 클래스

멤버 클래스의 인스턴스는 외부 클래스의 인스턴스에 종속적입니다.

 

class Outer {
   private int num = 0;
   class Member {     // 멤버 클래스의 정의
      void add(int n) { num += n; }
      int get() { return num; }
   }
}
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());

   }

}

 

멤버 클래스는 클래스의 정의를 감추어야 할 때 유용하게 사용이 됩니다. 

가령 메인 클래스에서 인스턴스를 생성해서 사용한 클래스를 감출 때 사용합니다. 

 

//멤버 클래스 Bean을 포함한 외부 클래스 Coffee

class Coffee{
	void getCoffee() {
		new Bean().thisIs();		
	}
	
	class Bean{
		void thisIs() {
			System.out.println("에티오피아 원두");
		}
	}
}


public class Nested01 {
	public static void main(String[] args) {
		
		
		Coffee c = new Coffee(); 
		c.getCoffee();
	}
}

원두를.. 감췄어..


(2) 로컬 클래스 

 

로컬 클래스는 멤버 클래스와 상당부분 유사하고, 지역 내에 정의되는 점에서만 차이가 있습니다. 

 

class Bag{	// 가방 클래스 안에 
	void getPencil() {	// 겟 펜슬 메소드 (지역 변수) 
		class Pencil{		// 그 안의 펜슬 클래스 
			public String toString() {		// 그 안의 연필 값
				return "연필";
			}
		}
		Pencil p = new Pencil();	// 겟 펜슬 메소드 안에서만 쓸 수 있는 펜슬 
		System.out.println(p);
	}
	// Pencil p = new Pencil(); 겟 펜슬 메소드 바깥에서는 사용할 수 없음. 
}




public class Nested01 {
	public static void main(String[] args) {
	
		new Bag().getPencil(); 
	}
}

더 깊이 숨겨..


(3) 익명 클래스 

 

일회성으로 새롭게 정의하여 사용하는 클래스입니다. 

한 번 구현하고 마는 중괄호의 표현입니다.  

사용법을 로컬 클래스와 비교해서 확인해볼게요. 

 

로컬 클래스  익명 클래스
public Printable getPrinter() {
   class Printer implements Printable { // 로컬 클래스 Printer의 정의
      public void print() {
         System.out.println(con);
      }
   }
  
   return new Printer(); // Printer 인스턴스의 생성
}
interface Printable { void print(); }

public Printable getPrinter() {
   return new Printable() {    // 익명 클래스의 정의와 인스턴스 생성
      public void print() {
         System.out.println(con);
      }
   };
}

 

즉, 이름을 정의하지 않은 채 직접적으로 클래스를 작성함으로써 일회성의 클래스르 사용하는 것이에요. 

 

package day19;

interface Player{
    void training();
   
}


public class Nested02 {

    public static void main(String[] args) {
        // 인터페이스 플레이어의 추상 메소드가 정의되지 않아서 사용할 수 없는데
         //그것을 인스턴스 생성시에 같이 일회성으로 정의를 하는 것.
       
        Player son = new Player() {
            public void training() {
                System.out.println("드리블 연습.");
            }
        };      // 익명 클래스 정의 뒤에는 세미콜론이 필수 입니다.

        son.training();
       
    }
}
 
일반 클래스나 추상 클래스에서도 동일한 작업을 할 수도 있지만 
주로 인터페이스 클래스에서 주로 사용 합니다. 
아래처럼 클래스를 추가할 때 별도의 클래스를 지정하지 않고 사용하는 경우가 있기 때문입니다. 
 
package day19;

interface Player{
	void training(); 
	
}


public class Nested02 {

	public static void main(String[] args) {
		// 인터페이스 플레이어의 추상 메소드가 정의되지 않아서 사용할 수 없는데 
		 //그것을 인스턴스 생성시에 같이 일회성으로 정의를 하는 것.
		
		Player son = new Player() {
			public void training() {
				System.out.println("드리블 연습.");
			}
		};		// 익명 클래스 정의 뒤에는 세미콜론이 필수 입니다.
		
		Player park = new Player() {
			public void training() {
				System.out.println("변화구 연마.");
			}
		};		 

		son.training();
		park.training();
		
	}
}
 

 

마지막으로 사용의 예시에 대해서 한 번 더  확인을 해볼게요. 

왼쪽은 익명 클래스를 사용하지 않을 때고 오른쪽은 익명 클래스를 사용할 것입니다. 

 

class StrComp implements Comparator<String> {
   @Override
   public int compare(String s1, String s2) {
      return s1.length() - s2.length();
   }
}
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);
   }
}
class AnonymousComparator {
   public static void main(String[] args) {
      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 s1, String s2) {
            return s1.length() - s2.length();
         }
      };
     
      Collections.sort(list, cmp);
      System.out.println(list);
   }
}

두 코딩의 결과는 같습니다. 

 

이후에 람다를 배울 때 인터페이스의 익명 클래스의 구현과 유사한 점이 있기 때문에 숙지해두는 것이 좋습니다. 

 

반응형