본문 바로가기

항해 99/Java

Java - Nested, Inner Class

참조 - 김영한 자바 중급 1편 강의

 

중첩 클래스, 내부 클래스(Nested Class, Inner Class)

아래 예시 코드처럼 클래스 안에 클래스를 중첩해서 정의할 수 있는데, 이를 중첩 클래스(Nested Class)라 한다.

class Outer {
	...
    // 중첩 클래스
    class Nested {
    	...
    }
}

 

중첩 클래스는 클래스를 정의하는 위치에 따라 다음과 같이 분류한다.

 

중첩 클래스는 총 4가지가 있고, 크게 2가지로 분류할 수 있다.

  • 정적 중첩 클래스
  • 내부 클래스 종류
    • 내부 클래스
    • 지역 클래스
    • 익명 클래스

중첩 클래스를 정의하는 위치는 변수의 선언 위치와 같다.

 

변수의 선언 위치

  • 정적 변수(클래스 변수)
  • 인스턴스 변수
  • 지역 변수

중첩 클래스의 선언 위치

  • 정적 중첩 클래스 → 정적 변수와 같은 위치
  • 내부 클래스 → 인스턴스 변수와 같은 위치
  • 지역 클래스 → 지역 변수와 같은 위치

중첩(Nested)과 내부(Inner)를 분류하는 핵심

  • 정적 중첩 클래스는 바깥 클래스와 전혀 다른 클래스로 바깥 클래스의 인스턴스에 소속되지 않는다.
  • 내부 클래스는 바깥 클래스를 구성하는 요소로 바깥 클래스의 인스턴스에 소속된다.

즉, 내부 클래스들은 바깥 클래스의 인스턴스에 소속되지만, 정적 중첩 클래스는 그렇지 않다.

 

정적 중첩 클래스

  • static이 붙는다.
  • 바깥 클래스의 인스턴스에 소속되지 않는다.

내부 클래스

  • static이 붙지 않는다.
  • 바깥 클래스의 인스턴스에 소속된다.

내부 클래스의 종류

  • 내부 클래스(inner class): 바깥 클래스의 인스턴스 멤버에 접근
  • 지역 클래스(local class): 내부 클래스의 특징 + 지역 변수에 접근
  • 익명 클래스(anonymous class): 지역 클래스의 특징 + 클래스의 이름이 없는 특별한 클래스

용어 정리

  • 중첩 클래스: 정적 중첩 클래스 + 내부 클래스 종류 모두 포함
  • 정적 중첩 클래스: 정적 중첩 클래스를 말함
  • 내부 클래스: 내부 클래스, 지역 클래스, 익명 클래스를 포함해서 말함

중첩 클래스 사용?

  • 특정 클래스가 다른 하나의 클래스 안에서만 사용되거나, 둘이 아주 긴밀하게 연결되어 있는 특별한 경우에만 사용(외부의 여러 클래스가 특정 중첩 클래스를 사용하는 경우 중첩 클래스로 만들면 안 됨)

사용 이유

  • 논리적 그룹화: 특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스 안에 포함하는 것이 논리적으로 더 그룹화 됨, 다른 곳에서 사용될 필요가 없는 중첩 클래스가 외부에 노출되지 않는 장점이 있다.
  • 캡슐화: 중첩 클래스는 바깥 클래스의 private 멤버에 접근할 수 있고, 불필요한 public 메서드를 제거할 수 있다.

 

정적 중첩 클래스

package nested.nested;

public class NestedOuter {

    private static int outClassValue = 3;
    private int outInstanceValue = 2;

    static class Nested {
        private int nestedInstanceValue = 1;

        public void print() {
            // 자신의 멤버에 접근
            System.out.println(nestedInstanceValue);

            // 바깥 클래스의 인스턴스 멤버에 접근에는 접근할 수 없다.
            // System.out.println(outInstanceValue);

            // 바깥 클래스의 클래스 멤버에는 접근할 수 있다. private도 접근 가능
            System.out.println(outClassValue);
        }
    }
}
  • 정적 중첩 클래스는 앞에 static이 붙는다.
  • 정적 중첩 클래스는
    • 자신의 멤버에 접근 가능
    • 바깥 클래스의 인스턴스 멤버에는 접근 불가
    • 바깥 클래스의 클래스 멤버에는 접근 가능
  • 정적 중첩 클래스는 바깥 클래스의 private 접근 제어자에 접근할 수 있다.
package nested.nested;

public class NestedOuterMain {

    public static void main(String[] args) {
        NestedOuter outer = new NestedOuter();
        NestedOuter.Nested nested = new NestedOuter.Nested();
        nested.print();

        System.out.println("nestedClass = " + nested.getClass());
    }
}
  • 정적 중첩 클래스는 new 바깥클래스.중첩클래스()로 생성할 수 있음
  • 중첩 클래스는 NestedOuter.Nested와 같이 바깥 클래스.중첩클래스로 접근할 수 있음

 

바깥 클래스의 멤버에 접근

 

정적 중첩 클래스는 다른 클래스를 중첩해 둔 것이므로 둘은 아무런 관계가 없다.

 

정적 중첩 클래스 활용

리팩토링 전

package nested.nested.ex1;

// Network 객체 안에서만 사용
public class NetworkMessage {

    private String content;

    public NetworkMessage(String content) {
        this.content = content;
    }

    public void print() {
        System.out.println(content);
    }
}
package nested.nested.ex1;

public class Network {

    public void sendMessage(String text) {
        NetworkMessage networkMessage = new NetworkMessage(text);
        networkMessage.print();
    }
}
 
 
package nested.nested.ex1;

public class NetworkMain {

    public static void main(String[] args) {
        Network network = new Network();
        network.sendMessage("hello java");
    }
}

 

리팩토링 후

package nested.nested.ex2;

public class Network {

    public void sendMessage(String text) {
        NetworkMessage networkMessage = new NetworkMessage(text);
        networkMessage.print();
    }

    private static class NetworkMessage {

        private String content;

        public NetworkMessage(String content) {
            this.content = content;
        }

        public void print() {
            System.out.println(content);
        }
    }
}
package nested.nested.ex2;

public class NetworkMain {

    public static void main(String[] args) {
        Network network = new Network();
        network.sendMessage("hello java");
    }
}

 

Network 클래스에 NetworkMessage 클래스를 중첩하는 것으로 Network 클래스에서 접근할 수 있도록 하고 private 접근 제어자를 사용해 외부에서 접근할 수 없도록 하여 불필요한 코드를 줄였다.

 

중첩 클래스의 접근

내 클래스에 포함된 중첩 클래스가 아니라 다른 곳에 있는 중첩 클래스에 접근할 대는 바깥 클래스.중첩클래스로 접근해야 한다.

NestedOuter.Nested nested = new NestedOuter.Nested();

 

내부 클래스

내부 클래스는 바깥 클래스의 인스턴스를 이루는 요소가 된다. 즉, 내부 클래스는 바깥 클래스의 인스턴스에 소속된다.

package nested.inner;

public class InnerOuter {

    private static int outClassValue = 3;
    private int outInstanceValue = 2;

    class Inner {
        private int innerInstanceValue = 1;

        public void print() {
            // 자기 자신에 접근
            System.out.println(innerInstanceValue);

            // 외부 클래스의 인스턴스 멤버에 접근 가능, private도 접근 가능
            System.out.println(outInstanceValue);

            // 외부 클래스의 멤버에 접근 가능, private도 접근 가능
            System.out.println(outClassValue);
        }
    }
}
  • 내부 클래스는 앞에 static이 붙지 않는다. 인스턴스 멤버가 된다.
  • 내부 클래스는
    • 자신의 멤버에 접근 가능
    • 바깥 클래스의 인스턴스 멤버에 접근 가능
    • 바깥 클래스의 클래스 멤버에 접근 가능
  • 내부 클래스는 바깥 클래스의 private 접근 제어자에 접근할 수 있다.
package nested.inner;

public class InnerOuterMain {

    public static void main(String[] args) {
        InnerOuter outer = new InnerOuter();
        InnerOuter.Inner inner = outer.new Inner();
        inner.print();

        System.out.println("innerClass = " + inner.getClass());
    }
}
  • 내부 클래스는 바깥 클래스의 인스턴스 정보를 알아야 생성할 수 있다.
  • 바깥 클래스의 인스턴스 참조.new 내부클래스()로 생성 가능하다.
    • 바깥 클래스의 인스턴스에 소속되어야 하기 때문에 바깥 클래스의 인스턴스 참조가 필요하다.
  • outer.new Inner()로 생성한 내부 클래스는 개념상 바깥 클래스의 인스턴스 내부에 생성된다.
    • 바깥 클래스의 인스턴스를 먼저 생성해야 내부 클래스의 인스턴스를 생성할 수 있다.

개념 - 내부 클래스의 생성

실제 - 내부 클래스의 생성

 

  • 실제로 내부 인스턴스가 바깥 인스턴스에 생성되는 것이 아니라 바깥 인스턴스의 참조를 보관하고 이를 통해 바깥 인스턴스 멤버에 접근할 수 있는 것이다.
  • 개념상 인스턴스 안에 생성된다고 이해하는 것이 편하다.

내부 클래스의 활용

리팩토링 전

package nested.inner.ex1;

// Car에서만 사용
public class Engine {

    private Car car;

    public Engine(Car car) {
        this.car = car;
    }

    public void start() {
        System.out.println("충전 레벨 확인: " + car.getChargeLevel());
        System.out.println(car.getModel() + "의 엔진을 구동합니다.");
    }
}
package nested.inner.ex1;

public class Car {

    private String model;
    private int chargeLevel;
    private Engine engine;

    public Car(String model, int ChargeLevel) {
        this.model = model;
        this.chargeLevel = ChargeLevel;
        this.engine = new Engine(this);
    }

    // Engine에서만 사용하는 메서드
    public String getModel() {
        return model;
    }

    // Engine에서만 사용하는 메서드
    public int getChargeLevel() {
        return chargeLevel;
    }

    public void start() {
        engine.start();
        System.out.println(model + " 시작 완료");
    }
}
package nested.inner.ex1;

public class CarMain {

    public static void main(String[] args) {
        Car myCar = new Car("Model Y", 100);
        myCar.start();
    }
}

 

리팩토링 후

package nested.inner.ex2;

public class Car {

    private String model;
    private int chargeLevel;
    private Engine engine;

    public Car(String model, int ChargeLevel) {
        this.model = model;
        this.chargeLevel = ChargeLevel;
        this.engine = new Engine();
    }

    public void start() {
        engine.start();
        System.out.println(model + " 시작 완료");
    }

    private class Engine {

        public void start() {
            System.out.println("충전 레벨 확인: " + chargeLevel);
            System.out.println(model + "의 엔진을 구동합니다.");
        }
    }
}
package nested.inner.ex2;

public class CarMain {

    public static void main(String[] args) {
        Car myCar = new Car("Model Y", 100);
        myCar.start();
    }
}

 

리팩토링 전의 문제

  • Car 클래스는 엔진에 필요한 메서드들을 제공해야 하는데, 해당 메서드들은 엔진에서만 사용되고 다른 곳에서 사용되지 않는다.
  • 결과적으로 엔진에서만 사용하는 기능을 위해 메서드를 추가해서, 모델과 이름과 충전 레벨을 외부에 노출해야 한다.

리팩토링을 통해 엔진에서만 사용되는 메서드를 제거하고 꼭 필요한 메서드만 외부에 노출함으로써 Car의 캡슐화를 더 높일 수 있었다.

 

지역 클래스

지역 클래스(Local class)는 내부 클래스의 특별한 종류의 하나로 내부 클래스의 특징을 그대로 가진다(지역 클래스도 내부 클래스이므로 바깥 크래스의 인스턴스 멤버에 접근할 수 있다).

 

지역 클래스는 지역 변수와 같이 코드 블럭 안에서 정의된다.

 

지역 클래스의 특징

  • 지역 클래스는 지역 변수처럼 코드 블럭 안에 클래스를 선언한다.
  • 지역 클래스는 지역 변수에 접근할 수 있다.
package nested.local;

public class LocalOuterV1 {

    private int outInstanceVar = 3;

    public void process(int paramVar) {
        int localVar = 1;

        class LocalPrinter {
            int value = 0;

            public void printData() {
                System.out.println("value = " + value);
                System.out.println("localVar = " + localVar);
                System.out.println("paramVar = " + paramVar);
                System.out.println("outInstanceVar = " + outInstanceVar);
            }
        }

        LocalPrinter printer = new LocalPrinter();
        printer.printData();
    }

    public static void main(String[] args) {
        LocalOuterV1 localOuter = new LocalOuterV1();
        localOuter.process(2);
    }
}

 

지역 클래스의 접근 범위

  • 자신의 인스턴스 변수인 value에 접근할 수 있다.
  • 자신이 속한 코드 블럭의 지역 변수인 localVar에 접근할 수 있다.
  • 자신이 속한 코드 블럭의 매개변수인 paramVar에 접근할 수 있다(매개 변수도 지역 변수의 한 종류이다).
  • 바깥 클래스의 인스턴스 멤버인 outInstanceVar에 접근할 수 있다.

지역 클래스는 지역 변수처럼 접근 제어자를 사용할 수 없다.

 

내부 클래스를 포함한 중첩 클래스들도 일반 클래스처럼 인터페이스를 구현하거나, 부모 클래스를 상속할 수 있다.

package nested.local;

public interface Printer {
    void print();
}
package nested.local;

public class LocalOuterV2 {

    private int outInstanceVar = 3;

    public void process(int paramVar) {
        int localVar = 1;

        class LocalPrinter implements Printer{
            int value = 0;

            @Override
            public void print() {
                System.out.println("value = " + value);
                System.out.println("localVar = " + localVar);
                System.out.println("paramVar = " + paramVar);
                System.out.println("outInstanceVar = " + outInstanceVar);
            }
        }

        LocalPrinter printer = new LocalPrinter();
        printer.print();
    }

    public static void main(String[] args) {
        LocalOuterV2 localOuter = new LocalOuterV2();
        localOuter.process(2);
    }
}

 

지역 클래스 - 지역 변수 캡처

변수의 생명 주기

 

지역 변수 캡처

자바는 지역 클래스의 인스턴스를 생성하는 시점에 필요한 지역 변수를 복사해서 생성한 인스턴스에 함께 넣어두는데, 이런 과정을 변수 캡처(Capture)라 한다(모든 지역 변수를 캡처하는 것이 아니라 접근이 필요한 지역 변수만 캡처한다).

 

지역 변수 캡처 예제

package nested.local;

import java.lang.reflect.Field;

public class LocalOuterV3 {

    private int outInstanceVar = 3;

    public Printer process(int paramVar) {

        int localVar = 1;   //지역 변수는 스택 영역이 종료되는 순간 함께 제거된다.

        class LocalPrinter implements Printer{
            int value = 0;

            @Override
            public void print() {
                System.out.println("value = " + value);

                //인스턴스는 지역 변수보다 더 오래 살아남는다.
                System.out.println("localVar = " + localVar);
                System.out.println("paramVar = " + paramVar);
                System.out.println("outInstanceVar = " + outInstanceVar);
            }
        }

        LocalPrinter printer = new LocalPrinter();
        //printer.print();를 여기서 실행하지 않고 Printer 인스턴스만 반환한다.
        return printer;
    }

    public static void main(String[] args) {
        LocalOuterV3 localOuter = new LocalOuterV3();
        Printer printer = localOuter.process(2);
        //printer.print()를 나중에 실행한다. process()의 스택 프레임이 사라진 이후에 실행
        printer.print();

        //추가
        System.out.println("필드 확인");
        Field[] fields = printer.getClass().getDeclaredFields();
        for (Field field : fields) {
            System.out.println("field = " + field);
        }
    }
}

 

지역 클래스의 인스턴스 생성과 지역 변수 캡처 과정

  • 1.LocalPrinter 인스턴스 생성 시도: 지역 클래스의 인스턴스를 생성할 때 지역 클래스가 접근하는 지역 변수를 확인한다.
    • LocalPrinter 클래스는 paramVar, localVar 지역 변수에 접근한다.
  • 2.사용하는 지역 변수 복사: 지역 클래스가 사용하는 지역 변수를 복사한다(매개변수도 지역 변수의 한 종류이다).
    • paramVar, localVar 지역 변수를 복사한다.

  • 3. 지역 변수 복사 완료: 복사한 지역 변수를 인스턴스에 포함한다.
  • 4. 인스턴스 생성 완료: 복사한 지역 변수를 포함해서 인스턴스 생성이 완료된다(복사한 지역 변수를 인스턴스를 통해 접근 가능)

 

  • LocalPrinter 인스턴스에서 printer() 메서드를 통해 paramVar, localVar에 접근하면 인스턴스에 있는 캡처한 변수에 접근한다.
  • 캡처한 지역 변수의 생명주기는 인스턴스 생명주기와 같기 때문에 인스턴스는 지역 변수의 생명주기와 무관하게 캡처 변수에 접근할 수 있다.
  • 이렇게 해서 지역 변수와 지역 클래스를 통해 생성한 인스턴스의 생명주기가 다른 문제를 해결한다.

지역 변수 캡처 시 주의 사항

지역 클래스가 접근하는 지역 변수는 절대로 중간에 값이 변하면 안된다(final로 선언하거나 사실상 final이어야 한다).

용어 - 사실상 final
영어로 effectively final이라 한다. 사실상 final 지역 변수는 지역 변수에 final 키워드를 사용하지는 않았지만,
값을 변경하지 않는 지역 변수를 뜻한다. final 키워드를 넣지 않았을 뿐이지, 실제로는 final 키워드를 넣은 것 처
럼 중간에 값을 변경하지 않은 지역 변수이다. 따라서 사실상 final 지역 변수는 final 키워드를 넣어도 동일하게
작동해야 한다.

 

지역 클래스가 접근하는 지역 변수가 final 또는 사실상 final이어야 하는 이유

  • 지역 변수의 값을 변경하면 인스턴스에 캡처한 변수의 값도 변경해야 한다.
  • 반대로 인스턴스에 있는 캡처 변수의 값을 변경하면 해당 지역 변수의 값도 다시 변경해야 한다.
  • 개발자 입장에서 보면 예상하지 못한 곳에서 값이 변경될 수 있다. 이는 디버깅을 어렵게 한다.
  • 지역 변수의 값과 인스턴스에 있는 캡처 변수의 값을 서로 동기화 해야 하는데, 멀티 쓰레드 상황에서 이런 동기화는 매우 어렵고, 성능에 나쁜 영향을 줄 수 있다.

 

익명 클래스

익명 클래스는 지역 클래스의 특별한 종류의 하나로 지역 클래스인데, 클래스 이름이 없다는 특징이 있다.

package nested.anonymous;

import nested.local.Printer;

public class AnonymousOuter {

    private int outInstanceVar = 3;

    public void process(int paramVar) {
        int localVar = 1;

        Printer printer = new Printer() {
            int value = 0;

            @Override
            public void print() {
                System.out.println("value = " + value);
                System.out.println("localVar = " + localVar);
                System.out.println("paramVar = " + paramVar);
                System.out.println("outInstanceVar = " + outInstanceVar);
            }
        };
        printer.print();
        System.out.println("printer.class= " + printer.getClass());
    }

    public static void main(String[] args) {
        AnonymousOuter main = new AnonymousOuter();
        main.process(2);
    }
}

 

익명 클래스를 사용하면 클래스의 이름을 생략하고, 클래스의 선언과 생성을 한 번에 처리할 수 있다.

Printer printer = new Printer(){
	//body
}

 

익명 클래스는 클래스의 본문(body)을 정의하면서 동시에 생성한다. new 다음에 바로 상속 받으면서 구현할 부모 타입을 입력하면 된다.

 

위 코드는 인터페이스를 생성하는 것이 아니고, Printer라는 이름의 인터페이스를 구현한 익명 클래스를 생성하는 것이고, {body} 부분에 Printer 인터페이스를 구현한 코드를 작성하면 된다.

 

익명 클래스 특징

  • 익명 클래스는 이름 없는 지역 클래스를 선언하면서 동시에 생성한다.
  • 익명 클래스는 부모 클래스를 상속 받거나, 또는 인터페이스를 구현해야 한다.
    • 익명 클래스를 사용할 때는 상위 클래스나 인터페이스가 필요하다.
  • 익명 클래스는 말 그대로 이름이 없다. 이름을 가지지 않으므로, 생성자를 가질 수 없다. (기본 생성자만 사용됨)
  • 익명 클래스는 AnonymousOuter$1과 같이 자바 내부에서 바깥 클래스 이름 + $ + 숫자로 정의된다.

익명 클래스의 장점

익명 클래스를 사용하면 클래스를 별도로 정의하지 않고도 인터페이스나 추상 클래스를 즉석에서 구현할 수 있어 코드가 더 간결해진다. 단, 복잡하거나 재사용이 필요한 경우에는 별도의 클래스를 정의하는 것이 좋다.

 

익명 클래스는 단 한 번만 인스턴스를 생성할 수 있기 때문에, 여러 번 생성이 필요한 경우에는 익명 클래스를 사용할 수 없다.

 

익명 클래스 활용

예제 코드를 익명 클래스를 사용해 리팩토링 할 수 있다.

리팩토링 전

package nested.anonymous.ex;

public class Ex0Main {

    public static void helloJava() {
        System.out.println("프로그램 시작");
        System.out.println("Hello Java");
        System.out.println("프로그램 종료");
    }

    public static void helloSpring() {
        System.out.println("프로그램 시작");
        System.out.println("Hello Spring");
        System.out.println("프로그램 종료");
    }

    public static void main(String[] args) {
        helloJava();
        helloSpring();
    }
}

 

리팩토링 후

package nested.anonymous.ex;

public class Ex0Ref0Main {

    public static void hello(String lang) {
        System.out.println("프로그램 시작");
        System.out.println("Hello " + lang);
        System.out.println("프로그램 종료");
    }

    public static void main(String[] args) {
        hello("Java");
        hello("Spring");
    }
}
  • 익명 클래스를 통해 코드의 변하지 않는 부분과 변하는 부분을 분리해서 처리하는 것으로 코드의 중복을 줄일 수 있다.
  • 변하는 부분과 변하지 않는 부분을 분리하고, 변하는 부분을 외부에서 전달 받으면, 메서드(함수)의 재사용성을 높일 수 있다.

예제 2 - 리팩토링 전

package nested.anonymous.ex;

import java.util.Random;

public class Ex1Main {

    public static void helloDice() {
        System.out.println("프로그램 시작");

        //코드 조각 시작
        int randomValue = new Random().nextInt(6) + 1;
        System.out.println("주사위 = " + randomValue);
        //코드 조각 종료

        System.out.println("프로그램 종료");
    }

    public static void helloSum() {
        System.out.println("프로그램 시작");

        //코드 조각 시작
        for (int i = 0; i < 3; i++) {
            System.out.println("i = " + i);
        }
        //코드 조각 종료

        System.out.println("프로그램 종료");
    }

    public static void main(String[] args) {
        helloDice();
        helloSum();
    }
}

 

예제 2 - 리팩토링 후

package nested.anonymous.ex;

import java.util.Random;

public class Ex1RefMainV4 {

    public static void hello(Process process) {
        System.out.println("프로그램 시작");

        //코드 조각 시작
        process.run();
        //코드 조각 종료

        System.out.println("프로그램 종료");
    }

    public static void main(String[] args) {

        System.out.println("Hello 실행");
        hello(new Process() {

            @Override
            public void run() {
                int randomValue = new Random().nextInt(6) + 1;
                System.out.println("주사위 = " + randomValue);
            }
        });

        hello(new Process() {

            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    System.out.println("i = " + i);
                }
            }
        });
    }
}
  • 익명 클래스를 사용하고 익명 클래스의 참조값을 인수로 바로 전달하는 것으로 코드의 불필요한 중복을 줄이고 재사용성을 높였다.

 

람다(lambda)

자바 8 이전까지 메서드에 인수로 전달할 수 있는 것은 크게 2가지로

  • int, double과 같은 기본형 타입
  • Process, Member와 같은 참조형 타입(인스턴스)

메서드에 인수로 전달할 수 있는 것은 간단한 데이터나, 인스턴스의 참조이다.

 

코드 조각을 전달하기 위해 클래스를 정의하고 메서드를 만들고 또 인스턴스를 생성해서 전달해야 하는 과정을 줄이는 것이 더 좋을 것이다. 자바 8에 들어서면서 메서드(함수)를 인수로 전달할 수 있게 되었고, 간단히 람다(lambda)라 한다.

 

리팩토링 - 람다

package nested.anonymous.ex;

import java.util.Random;

public class Ex1RefMainV5 {

    public static void hello(Process process) {
        System.out.println("프로그램 시작");

        //코드 조각 시작
        process.run();
        //코드 조각 종료

        System.out.println("프로그램 종료");
    }

    public static void main(String[] args) {

        System.out.println("Hello 실행");
        hello(() -> {
            int randomValue = new Random().nextInt(6) + 1;
            System.out.println("주사위 = " + randomValue);
        });

        hello(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("i = " + i);
            }
        });
    }
}
  • 클래스나 인스턴스를 정의하지 않고, 메서드(함수)의 코드 블럭을 직접 전달할 수 있다.

 

정리

  • 정적 중첩 클래스: 바깥 클래스와 밀접한 관련이 있지만, 인스턴스 간에 데이터 공유가 필요 없을 때 사용한다.
  • 내부 클래스: 바깥 클래스의 인스턴스와 연결되어 있고, 바깥 클래스의 인스턴스 상태에 의존하거나 강하게 연관된 작업을 수행할 때 사용한다.
  • 지역 클래스:
    • 내부 클래스의 특징을 가진다.
    • 지역 변수에 접근할 수 있다. 접근하는 지역 변수는 final 이거나 사실상 final 이어야 한다.
    • 주로 특정 메서드 내에서만 간단히 사용할 목적으로 사용한다.
  •  익명 클래스:
    • 지역 클래스인데, 이름이 없다.
    • 상위 타입을 상속 또는 구현하면서 바로 생성된다.
    • 주로 특정 상위 타입을 간단히 구현해서 일회성으로 사용할 때 유용하다.

'항해 99 > Java' 카테고리의 다른 글

자바 - 탐색 알고리즘, 그래프 알고리즘  (0) 2024.08.22
Java - 정렬 알고리즘  (0) 2024.08.08
Java - 날짜와 시간  (0) 2024.07.26
Java - ENUM  (0) 2024.07.18
Java - Wrapper, Class, System, Math, Random  (0) 2024.06.13