infinity : 무한한 성장가능성

이펙티브 자바 item 24) 멤버클래스는 되도록 static으로 만들어라 본문

📖 book/❤️ Effective Java

이펙티브 자바 item 24) 멤버클래스는 되도록 static으로 만들어라

인피니 2022. 4. 17. 13:07
  • 중첩 클래스: 다른 클래스 안에 정의된 클래스를 말함
    • 중첩 클래스는 자신을 감싼 바깥 클래스에서만 쓰여한다
    • 종류
      • 정적 멤버 클래스
      • 비정적 멤버 클래스 (inner class)
      • 익명 클래스(inner class)
      • 지역 클래스 (inner class)

🤔 책에서는 설명하지 않지만.. 갑자기 위의 4종류가 헷갈려 책을 찾아 공부한 내용을 추가한다ㅎㅎㅎ

중첩 클래스와 네스티드 클래스는 동일한 말이다..

class Outer{
	class Nested{...} // 네스티드 클래스 
}

 네스티드 클래스는 static 선언 여부를 기준으로 나뉜다.

1) static 네스티드 클래스 (정적 멤버 클래스)

2) Non-static 네스티스 클래스 (비정적 멤버 클래스, 익명 클래스. 지역 클래스)

요기서! Non-static 네스티드 클래스를 이너 클래스라 한다

👉 이너 클래스는 정의되는 위치 or특성에 따라 다시 세 종류로 나뉘게 되는데..

💫 일단 Static 네스티드 클래스에 대해 먼저 알아보자

 

Static 네스티드 클래스

class Outer{
	private static int num=0;
    static class Nested{
    	void add(int n){
        	num+=1;
        }
    }
}

-> 코드 간격이 지저분한데.. 이게 아니 딱 맞게 수정이 안된다.... 🥲

-> static네스티드 클래스에서 Outer 클래스의 static 멤버 변수인 num을 접근하는 것을 볼 수 있다.

-> private이지만, 네스티드 클래스 안에서는 접근 가능하다.

-> 중요하게 볼 부분은 static 네스티드 클래스는 외부 클래스에 static으로 선언된 변수와 메서드에만 접근이 가능하다는 것이다

또한 외부 클래스(Outer 클래스)의 생성과 상관없이 static네스티드 클래스를 생성할 수 있다

class{
	public static void main(String[]args){
    	Outer.Nested nst=new Outer.Nested();
    }
}

 

👉 이너 클래스

이너 클래스의 종류인 멤버 클래스, 로컬 클래스, 익명 클래스중에 멤버 클래스, 로컬 클래스는 정의된 위치에 따라 구분이 됨

멤버 클래스는 인스턴스 변수, 인스턴스 메서드와 같은 위치에 정의됨

로컬 클래스는 중괄호 내에, 특히 메서드 내에 정의됨

익명 클래스는 클래스 이름이 존재하지 않음 

 

👉 이너 클래스인 멤버 클래스

class Outer{
    private int num=0;
    
    class Member{
    	void add(int n){
        	void add(int n){
            	num+=n;
            }
        }
        int get(){
        	return num;
        }
    }
}

->Member 클래스 내에서는 Outer클래스의 인스턴스 변수에 접근이 가능하다. 즉 인스턴스 변수가 private으로 선언되어 있어도 가능

-> 멤버 클래스의 인스턴스는 외부 클래스의 인스턴스에 종속적임 

class MemberInner {
	public static void main(String[]args){
    	Outer o1=new Outer();
        Outer.Member o1m1=o1.new Member();    
    }
}

-> 즉 멤버 클래스의 인스턴스를 생성하고 싶다면 외부 클래스의 인스턴스를 먼저 생성해야 함 

 

👉이너 클래스인 로컬 클래스

interface Printalbe{
	void print();
}

class Papers{
	private String con;
    public Papers(String s){
    	con=s;
    }
    
    public Printable getPriner(){
    	class Printer implements Printable{ //메소드 안으로 클래스 정의를 숨김 
        	public void print(){
            	System.out.println(con);
            }
        }
        return new Printer();
    }
}

-> 즉, 멤버 클래스와 차이는 메서드 안에 Printer클래스 정의가 위치한다는 것 

-> 이렇게 메소드 안에 클래스를 정의할 경우 해당 메소드 내에서만 인스턴스 생성이 가능하다

 

👉익명 클래스 

interface Printable{
	void print();
}

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

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

드디어..

각각의 중첩 클래스를 언제, 왜 사용해야 하는지 알아보자

먼저 정적 멤버 클래스에 대해 알아보자 

정적 멤버 클래스는 바깥 클래스와 함께 쓰일 때만 유용한 public  도우미 클래스로 쓰인다.

 

-> 책에서는 해당 코드를 Calculator클래스의 public 정적 멤버 클래스가 된다면 CalculatorOpeation.PLUS처럼 같은 형태로 원하는 연산을 참조할 수 있다는 것을 예로 보여주고 있다.

 

🤔 정적 멤버 클래스는 알겠고, 비정적 멤버 클래스와는 무슨 차이가 더 있을까?

 

구문상에는 단지 static이 있고 없고의 차이지만, 의미상 차이는 꽤 크다!!!

비정적 멤버 클래스의 인스턴스는 바깥 클래스의 인스턴스와 암묵적으로 연결되어 비정적 멤버 클래스의 인스턴스 메서드에서 this를 사용해 바깥 인스턴스의 메서드를 호출 & 참조를 가져올 수 있음

바깥 클래스와 비정적 멤버 클래스의 관계는 비정적 멤버 인스턴스에 저장되며, 추가로 메모리 공간을 차지하게 되는 단점이 존재.. + 생성시간도 더 걸린다.

 

🤔 그럼 비정적 멤버 클래스는 어디에서 주로 쓰이는 걸까?

-> 비정적 멤버 클래스는 어댑터를 정의할 때 많이 사용된다.

ex) 어떤 클래스의 인스턴스를 감싸 마치 다른 클래스의 인스턴스처럼 보이게 하는 뷰로 사용됨 

ex) Map 인터페이스의 구현체들은 (keySet, entrySet, values메서드가 반환하는) 자신의 컬렉션 뷰를 구현할 때 비정적 멤버 클래스를 사용함 

ex) Set & List 같은 다른 컬렉션 인터페이스 구현들도 자신의 반복자를 구현할 때 비정적 멤버 클래스를 주로 사용 

pulbic class MySet<E> extends AbstractSet<E>{
//생략

@Override public Iterator<E> iterator(){
	return new MyIterator();
}

private class MyIterator implements Iterator<E>{

}}

 

즉, 멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조건 static을 붙여 정적 멤버 클래스로 만들자

왜??? 이렇게 책에서 주장하는 걸까?

이유) static을 생략하면 바깥 인스턴스와 숨은 외부 참조를  갖게 되는데 앞에 이야기한 것처럼 이 참조를 저장하려면 시간+ 공간 소비

가비지 컬렉션이 바깥 클래스의 인스턴스를 수거하지 못하는 메모리 누수가 생길 수 있음

->즉 하나의 외부 클래스의 인스턴스를 수거하지 못하면 비정적 멤버 인스턴스도 수거해가지 못하는 단점 존재

 

👉 익명 클래스

 

익명 클래스는 바깥 클래스의 멤버가 아니다. 

왜냐? 메서드가 실행될 때 익명 클래스는 생성되기 때문에 쓰이는 시점 (메서드가 호출되는 시점)에 동시에 인스턴스가 만들어짐 

즉, 필요할 때, 코드 어디에서든 만들 수 있다.

비정적 멤버 클래스는 바깥 클래스 인스턴스가 생성될 때 자동 생성 

익명 클래스는 응용하는데 제약이 많다.

1) 선언한 지점에서만 인스턴스를 만들 수 있음

-> 메서드를 호출할 때만 인스턴스를 생성할 수 있다는 말 같다.

2) instanceof 검사 불가 

3) 클래스의 이름이 필요한 작업 수행 x 

4) 익명 클래스를 사용하는 클라이언트는 그 익명 클래스가 상위 타입에서 상속한 멤버 외에는 호출할 수 없음

-> 이건 무슨 말인지 모르겠다..

5) 익명 클래스는 표현식 중간에 등장하기때문에 (10줄 이하로) 짧지 않으면 가독성이 떨어진다.

 

그래서 익명클래스는 어디에 주로 사용된 걸까?

1) 자바가 람다를 지원하기 전에는 즉석에서 작은 함수 객체나 처리 객체를 만드는 데 사용 

2) 정적 팩토리 메서드를 구현할 때  

 

👉 지역 클래스

4가지 중첩 클래스 중 가장 드물게 사용함