자바에서는 클래스와 클래스 간에 연관 혹은 의존 관계가 있는데, 이 관계가 일회성일 때에 관계는 표현하되, 각각 객체를 생성하고 레퍼런스 변수에 이 값을 담아주는 복잡한 과정을 생략할 수 있는 '내부 클래스(Inner Class/Nested Class)' 를 지원한다. (이 포스팅에서는 다루지 않지만 내부 클래스와 유사한 형태로 내부 인터페이스 또한 존재한다.)
내부 클래스는 이름 그대로 클래스의 내부에 클래스가 포함되는 것이다.
그런데 이런 내부 클래스도 변수와 같이 선언되는 위치에 따라 scope와 accessibiliy가 조금씩 달라진다.
<내부 클래스의 종류>
Class Shop {
int name;
Item item;
class Cashier { // 인스턴스 클래스
void payOut(Item item){
...
}
}
}
스태틱 클래스: 인스턴스 클래스와 같은 위치, static 멤버처럼 다루어짐. 외부 클래스의 static 클래스에서 자주 사용된다.
지역 클래스: 메서드나 초기화 블럭 안에 선언. 선언된 블락 안에서만 사용 가능
익명 클래스: 클래스의 선언과 객체의 생성을 동시에 하는 이름 없는 클래스
내부 클래스에서 static 변수를 정의 가능할까?
(멤버 메서드에서와 마찬가지로) 메서드가 static일 때만 static 변수를 정의 가능하다.
내부 클래스에서 외부 클래스의 static 멤버 변수를 사용 가능할까?
(멤버 메서드에서와 마찬가지로) 항상 사용 가능하다.(static 멤버 변수는 메모리 할당 시점이 프로그램 시작 시점이므로 어디에서든 바로 사용 가능하다)
내부 클래스에서 외부 클래스의 인스턴스 멤버 변수를 사용 가능할까?
(멤버 메서드에서와 마찬가지로) static 클래스의 경우 메모리 할당 시점이 사용하고자 하는 인스턴스 변수보다 앞서므로, 이를 맞춰주기 위해 static 클래스 내에서 외부 클래스의 객체를 생성한 이후에만 사용이 가능하다.
한편, final static 과 같은 경우 이전에도 말했듯이
그 자체로 상수를 의미하기 때문에 인스턴스 클래스에서도 선언 가능하다.
final static int LIMIT = 300; // 그 자체로 상수를 의미
Cart cart = new Cart();
Cart.Item item = cart.new Item(1, "galaxy S20", 1000);
Cart.Item item = new Cart.Item(1, "galaxy S20", 1000);
추상 클래스나 인터페이스를 오버라이딩해서 사용하기 위해서는
1. 오버라이딩 하는 클래스 파일 생성
2. 오버라이딩 메서드 작성
3. 해당 클래스의 객체 생성
4. 오버라이딩 메서드 호출
과 같은 과정을 거쳐야 한다.
일회성으로 사용하기 위해서 이런 과정을 다 거치는 것은 너무 번거로운 일이기 때문에 등장한 것이 익명 클래스이다.
MessageSender라는 인터페이스가 있고, 인터페이스에 send라는 추상 메서드가 있다면, 아래와 같이 익명 클래스 형태로 구현할 수 있다.
MessageSender obj = new MessageSender() {
@Override
void send(String message) {
System.out.println(message);
}
}
obj.send("메세지 전송");
여기서 MessageSender는 클래스의 이름이 아니라 인터페이스의 이름이다. 언뜻 보기에는 인스턴스화를 할 수 없는 추상 클래스나 인터페이스를 인스턴스화한 것처럼 보이지만, 익명 클래스를 구현한 것이다.
내부 클래스와 함께 람다식을 소개하는 이유는, 람다식을 이용하면 번거로운 과정을 생략하고 인터페이스를 오버라이딩해 사용할 수 있는 익명 클래스와 같은 작업을 익명 클래스보다도 한결 간결한 코드로 구현할 수 있기 때문이다.
예를 들어, 위의 예시를 람다식으로 표현하면 다음과 같다.
MessageSender obj = (String message) -> System.out.println(message);
이처럼 람다식을 활용하면 오버라이딩 하고 싶은 메서드의 이름조차도 적지 않을 수 있으며, 추론 가능한 범위 내에서 리턴값과 매개 변수의 데이터 타입 또한 생략할 수 있다.(람다식을 다루기 위한 인터페이스는 함수형 인터페이스
라고 하며(@FunctionalInterface) 람다식과의 1:1 맵핑
을 위해 여기에는 오직 하나의 추상 메서드만 정의되어 있어야 한다는 제약이 있다.)
람다식은 익명 클래스의 객체와 동등한 존재이다. 이 말인 즉슨,
타입 f = (int a, int b) -> a > b ? a : b;
와 같은 람다식이 있을 때, 생성된 람다식이라는 익명 객체의 주소는 참조변수 f에 저장된다. 이 참조변수를 통해 람다식과 맵핑된 메서드를 호출할 수 있으며, 참조변수의 타입은 클래스 또는 인터페이스여야 한다.
이 말을 다시 쓰면 다음과 같다.
interfaceName f(참조변수) = 람다식