본문 바로가기
Web

[Java] 내부 클래스와 익명 클래스 그리고 람다

by DuncanKim 2022. 7. 26.
728x90

[Java] 내부 클래스(Inner Class)와 익명 클래스(Anonymous class) 그리고 람다(Lambda)

 

 

1. 들어가며, 내부 클래스는 왜 필요한가?

 

하나의 클래스 안에서만 쓰이는 객체를 생성하고 싶을 때가 있다. 혼자 개발을 한다면 다른 곳에 구현을 한 번 하고 다른 클래스에서는 구현을 안하는 방법으로 사용하면 된다. 그렇지만, 2명, 3명, ... 100명이 개발을 같이 한다면...? 주석을 꼼꼼히 남기지 않는 한 그것을 다른 곳에서 구현하는 사람이 나타날 것이다.

 

그래서 단발적으로 사용하고, 확장할 필요가 없는 객체를 구현해야 할 때 내부클래스를 사용한다. 클래스의 메서드 안에서 구현을 하고 끝내는 내부클래스가 생겨난 것이다.

 

사용 이유 정리

1. 프로그램 안에서 일시적(단발적)으로 한 번만 사용'되어야 하는' 객체일 경우
: ex. UI이벤트 처리, 스레드 객체 등

2. 재사용성이 없고, 확장성을 활용하는 것이 유지보수에서 불리할 때
: ex. 비즈니스 로직이 제각각이고 재사용성이 전혀 없는데 따로 클래스를 구현해야 하는 번거로움이 있을 때 

 

 

2. 익명클래스, 익명인터페이스의 원형, 내부 클래스, 내부 인터페이스

 

 

다음과 같은 코드 처럼 사용할 수 있다. 메인 '메서드' 안에 클래스가 포함된 것을 볼 수 있다. Listener1은 내부 클래스가 되는 것이다. 이 클래스는 View클래스 안에 있는 내부 인터페이스를 '구현'하고 있다.

 

View 클래스 안에는 OnClickListener라는 익명의 인터페이스가 있다.

 

클래스 내부에 쓰는 것과 밖에 쓰는 것의 차이는 클래스 내부에 익명클래스를 위치시킬 경우, 익명클래스는 그 클래스에서만 사용할 수 있고, 밖에 쓰면(defalut인 경우) 한 파일 안에 있는 클래스 모든 곳에서 쓸 수 있다.

 

class Main {
	public static void main(String[] args) {
		View.OnClickListener aOnClickListener;
        // 익명의 클래스
		class Listener1 implements View.OnClickListener{
			@Override
			public void onClick() {
				System.out.println("클릭되었다는 사실을 전달받았습니다.");
			}
		}
		aOnClickListener = new Listener1();
		aOnClickListener.onClick();
		// 출력 => 클릭되었다는 사실을 전달받았습니다.
	}
}
class View {
    //익명의 인터페이스	
	static interface OnClickListener {
		public void onClick();
	}
}

 

 

3. 인터페이스, 추상클래스가 생성자를 불러들일 수 없는 이유(객체를 생성할 수 없는 이유)

 

1. 일단은 내부 메서드가 구현이 안되어 있을 수 있다.

2. 인터페이스에서 생성자를 허용하면 여러 인터페이스를 동시에 구현할 수 있다는 문제가 발생한다. 클래스가 다른 생성자를 정의하는 여러 인터페이스를 구현할 때 클래스는 여러 생성자를 구현해야 하는 상황에 놓인다. 그런데 각 생성자는 하나의 인터페이스만 만족하지만 다른 인터페이스는 만족하지 않을 수 있다. 이렇게 각 생성자를 호출할 수도 있기 때문에 객체를 생성하는 것은 불가능하다.

 

그렇다면, 구현이 안되어 있는 메서드를 구현해 놓는다면 객체 생성이 가능할까?

 

 

1) 방법 1 : 익명 함수 사용

 

class Main {
	public static void main(String[] args) {
		View.OnClickListener aOnClickListener;
		aOnClickListener = new View.OnClickListener() {
			@Override
			public void onClick() {
				System.out.println("클릭되었다는 사실을 전달받았습니다.");
			}
		};		
		aOnClickListener.onClick();
		// 출력 => 클릭되었다는 사실을 전달받았습니다.
	}
}

class View {
	static interface OnClickListener {
		public void onClick();
	}
}

 

onClick() 메서드가 추상이지만, new 연산자를 통해 객체를 생성할 때, onClick() 메서드를 직접 구현해주면, 추상 또는 인터페이스라도 객체 생성이 가능하다.

 

인터페이스 내부의 onClick() 메서드는 아무것도 붙어있지 않아도 (abstract public)이 강제로 붙어져있다. 왜냐하면 무조건 외부에서 구현을 해주어야 하기 때문이다. 익명으로 구현을 할 때도 동일하게 public 접근 제한자를 붙여주어야 한다.

 

 

2) 방법 2 : 람다 함수 사용

 

class Main {
	public static void main(String[] args) {
		View.OnClickListener aOnClickListener;
		aOnClickListener = () -> {
			System.out.println("클릭되었다는 사실을 전달받았습니다.");
		};
		// 수정가능지역 끝
		
		aOnClickListener.onClick();
		// 출력 => 클릭되었다는 사실을 전달받았습니다.
	}
}

class View {
	static interface OnClickListener {
		public void onClick();
	}
}

 

한편 자바 8부터는 람다 함수를 도입하고 있는데, 람다 함수로도 구현이 가능하다. 람다 함수는 '단축 표현'이라고 생각하면 좋다.

구현해야 할 추상 클래스 또는 인터페이스 내부에 메서드가 하나이면, 그 메서드를 구현할 것이라는 것이 뻔하다.

그렇기 때문에  new 연산자를 사용해서 객체를 만들지 않고, 간단한 표현식으로 줄여서 사용하는 것이 람다함수이다. 클래스를 감싸는 중괄호가 생략이 되어 있다. 왜냐하면 메서드가 하나밖에 없고, 변수의 형식이 추상클래스 또는 인터페이스이기 때문에 그곳에 있는 메서드를 표시할 것이 분명하기 때문이다.

 

만약 인터페이스 내부의 메서드가 두 개 이상이라면, 위의 1)번 코드와 같이 여러 개를 구현해주어야 한다.

 

 

4. 람다 함수 활용

 

1) 람다 함수 선언 방법

 

메서드 인자를 대신해서 내부에서 사용하고 싶을 때,

객체참조변수.메서드((매개변수, ...) -> {실행문});

이런 형식으로 사용하면 된다.

 

 

2) 람다 함수 예시 코드(계산기)

 

class Main {
	public static void main(String[] args) {
		계산기 a계산기 = new 계산기();
		a계산기.num1 = 10;
		a계산기.num2 = 20;
		int 결과1 = a계산기.수행((num1, num2) -> {return num1 + num2;});
		System.out.printf("result1 : %d\n", 결과1); // 30
		int 결과2 = a계산기.수행((num1, num2) -> {return num1 - num2;});
		System.out.printf("result2 : %d\n", 결과2); // -10
		int 결과3 = a계산기.수행((num1, num2) -> {return num1 * num2;});
		System.out.printf("result3 : %d\n", 결과3); // 300
	}
}
class 계산기 {
	int num1;
	int num2;
	int 수행(식 a식) {
		return a식.실행(num1, num2);
	}
}
interface 식 {
	public int 실행(int num1, int num2);
}

 

계산기 클래스에서는 변수만을 담아 리턴하도록 되어 있고, 각 함수는 한 번 씩만 쓰이기 때문에, 함수 안에서 람다 함수를 사용해서 마무리 하고 있다. 위의 코드에서는 수행이라는 메서드 안에 num1, num2라는 인자를 넘기게 되어 있다. 내부에서 num1, num2를 사용할 수 있기 때문에 람다함수로 결과식을 얻을 수 있는 것이다.

 

 

 

5. 익명클래스는 왜 필요한가?

 

내부클래스에서 더 나아가서 이제는 클래스의 이름도 없애버린것이다... 다시 부를일 없는 정말 1회용 빨때같은 존재...

 

이름없는 무명의 클래스와 함수는 왜 필요할까? 나중에 다시 부를 이유가 없기 때문에 일회용으로 쓰고 버리는 용도로 만들어진다. 즉, 재사용이 되지 않을 때 우리는 이것들을 사용할 수 있다.

 

익명 클래스를 사용하는 이유는 다른 곳에서 사용하면 안되는 클래스를 따로 분리해두지 않고, 다른 사람들이 협업을 할 때도 사용해야 할 부분에서만 사용할 수 있도록 지시를 해놓는 하나의 방법이 될 수 있기 때문이다. 의도가 조금 더 분명해진다라고 할 수 있다.

 

사용 이유 정리

1. 클래스 내부에서 한 번'만' 생성되는 객체인 경우

2. 다른 클래스에서는 전혀 사용하지 않을 경우
: ex. 그 클래스 안에서만 써야하는 계산식, 이벤트를 구현할 때

 

6. 익명 클래스 구현 예시

 

아래는 OnClickListener 인터페이스를 단 한 번 익명클래스로 구현하여 객체 안의 함수를 사용하는 코드이다.

익명클래스 내부에는 onClick(), 숨쉬다() 두 개의 메서드가 있는 것이다.

 

만약 함수 2개도 필요없다면? 4번의 람다함수를 이용할 수 있다. 클래스 조차도 구현할 필요가 없는 것이다.

 

// 이번 작업 : 구독자 => OnClickListener 로 용어 변경
// OnClickListener => 클릭 구독자
// 클릭 구독자 => 누군가가 클릭되었다는 사실을 관찰하는 객체
// 클릭 구독자 => 누군가가 클릭되었다면, 그 즉시 그 사실을 전달받고 싶어하는 객체

import java.util.List;
import java.util.ArrayList;

class Main {
	public static void main(String[] args) {
		View aButton = new Button();
		// 홍길동 : 내가 너(뷰, 버튼)한테 나의 리모콘을 줄테니까, 너가 그것을 잘 가지고 있다가, 너가 클릭이 되면, 내 리모콘에 있는 버튼 중 하나를 눌러줘
		// 홍길동 : 나의 리모콘에는 버튼이 2개 있는데, 그 중에서 onClick을 호출해야 되는데.. 너가 그걸 잘 처리할 수 있을까?
		// 뷰 : 그건 걱정하지 않아도 됩니다. 당신이 나에게 준 홍길동 리모콘은, 어차피 onClick 버튼 빼고 모든 버튼이 제거됩니다. 즉 리모콘 형변환이 일어납니다.
		// 뷰 : 대신 당신 해야할 일은, OnClickEventListen가 되어야 한다는 것 입나다.
		// 홍길동 : 그럴줄 알고 그것(OnClickEventListen) 구현했습니다.
		aButton.addOnClickListener(new OnClickListener() {
			@Override
			public void onClick(String msg) {
				System.out.println("익명홍길동이 받은 메세지 : " + msg);
			}

			public void 숨쉬다() {
				System.out.println("홍길동 : 숨쉬다.");
			}
		});
		aButton.fireClickEvent();
		aButton.addOnClickListener((msg) -> {System.out.println("람다홍길순이 받은 메세지 : " + msg);});

		
		aButton.fireClickEvent();
	}
}
class View {
	private List<OnClickListener> onClickListenerList;
	
	View() {
		onClickListenerList = new ArrayList<>();
	}
	
	public void addOnClickListener(OnClickListener aListener) {
		onClickListenerList.add(aListener);
	}
	
	public void fireClickEvent() {
		System.out.println("뷰가 클릭되었습니다.");
		
		for ( OnClickListener aListener : onClickListenerList ) {
			aListener.onClick("뷰가 클릭됨");
		}
	}
}

class Button extends View {
	
}

interface OnClickListener {
	public void onClick(String msg);
}

 

 

 

 

 

728x90

'Web' 카테고리의 다른 글

[Spring] Servlet Container  (0) 2022.07.29
[Spring] Servlet과 Spring  (0) 2022.07.27
[Spring] MVC Pattern  (0) 2022.07.26
[Web] Web Request, Response(웹의 요청과 응답)  (0) 2022.07.25
[Spring] Observer Pattern  (0) 2022.07.24

댓글