본문 바로가기
JAVA/이재환의 자바 프로그래밍 입문

[Java] Ch.22 내부 클래스, 람다식

by ♡˖GYURI˖♡ 2023. 10. 25.
728x90

내부 클래스

자바에서는 클래스 안에 클래스를 선언할 수 있음

  • 안쪽에 있는 클래스 : 중첩 클래스(nested class)
  • 중첩 클래스를 가지고 있는 클래스 : 외부 클래스(outer class)

 

중첩 클래스 구분

  • static 중첩 클래스 : 중첩이지만 내부 클래스는 아님
  • non-static 중첩 클래스 : 내부 클래스(inner class)라고 부름
    • 멤버 내부 클래스(member inner class)
    • 지역 내부 클래스(local inner class)
    • 익명 내부 클래스(anonymous inner class)
// 변수
class MyClass {
	static int n1;	// 스태틱 변수
    int n2;		// 멤버 변수 (인스턴스 변수)
    
    public void myFunc() {
    	int n3;		// 지역 변수
    }
}
// 내부 클래스
class MyClass {
    static class NestedButNotInner {}	// 스태틱 중첩 클래스
    class c1 {}				// 멤버 내부 클래스
    	
    public void myFunc() {
    	class c2 {}				//지역 내부 클래스
    }
}

 

 

멤버 내부 클래스

  • 다른 클래스와는 연관되어 사용되지 않고 해당 클래스에서만 특정 클래스를 사용할 때 하나의 소스 파일로 묶어 관리를 편하게 할 수 있음
  • 외부 클래스는 내부 클래스를 멤버 변수처럼 사용할 수 있고, 내부 클래스는 외부 클래스의 자원을 직접 사용할 수 있는 장점이 있음
  • 멤버 내부 클래스는 외부 클래스 뒤에 .new를 붙임
class Outer1 
{
    private int speed = 10;
    
    class MemberInner1
    {
        // 외부 클래스의 자원(speed) 사용 가능
        public void move() 
        {
            System.out.printf("인간형 유닛이 %d 속도로 이동합니다.\n", speed);
        }
    }

    public void getMarine() 
    {
        MemberInner1 inner = new MemberInner1();
        inner.move();
    }
}

public class Ex01_MemberInner
{
    public static void main(String[] args)
    {
        Outer1 out = new Outer1();
        
        // out 기반으로 생성된 객체의 메서드 호출
        out.getMarine();

        // out 기반으로 내부 클래스 객체 생성
        Outer1.MemberInner1 inner = out.new MemberInner1();

        // inner 기반으로 생성된 객체의 메서드 호출
        inner.move();
    }
}

 

 

지역 내부 클래스

지역 내부 클래스는 클래스의 정의 위치가 메서드, if문, while문 같은 중괄호 블록 안에 정의된다는 점에서 멤버 내부 클래스와 구분됨

이러면 해당 메서드 안에서만 객체 생성이 가능해지므로 클래스의 정의를 깊이 숨기는 효과가 있음

class HumanCamp2 
{
    private int speed = 10;
    
    public void getMarine()
    {
        class Marine2
        {
            // 외부 클래스의 자원(speed) 사용 가능
            public void move() 
            {
                System.out.printf("인간형 유닛이 %d 속도로 이동합니다.\n", speed);
            }
        }

        Marine2 inner = new Marine2();
        inner.move();
    }
}

public class Ex02_LocalInner
{
    public static void main(String[] args)
    {
        HumanCamp2 hc = new HumanCamp2();
        hc.getMarine();
    }
}

 

 

익명 내부 클래스

지역 내부 클래스는 해당 메서드에서만 클래스 생성이 가능하므로 클래스명이 상당히 제한적으로 사용됨

그래서 클래스명을 생략해버리기도 함

이렇게 클래스명을 생략한 것이 익명 내부 클래스(inner anonymous class)

interface Unit3
{
    void move();
}

class HumanCamp3 
{
    private int speed = 10;

    public Unit3 getMarine() 
    {
        class Marine3 implements Unit3 
        {
            public void move() 
            {
                System.out.printf("인간형 유닛이 %d 속도로 이동합니다.\n", speed);
            }
        }

        return new Marine3();
    }
}

public class Ex03_AnonymousInner1
{
    public static void main(String[] args)
    {
        HumanCamp3 hc = new HumanCamp3();
        Unit3 unit = hc.getMarine();
        unit.move();
    }
}

interface Unit4
{
    void move();
}

class HumanCamp4 
{
    private int speed = 10;

    public Unit4 getMarine() 
    {
//        class Marine4 implements Unit4 
//        {
//            public void move() 
//            {
//                System.out.printf("인간형 유닛이 %d 속도로 이동합니다.\n", speed);
//            }
//        }
//        return new Marine4();
        
        // 이름이 없으므로 부모 클래스나 인터페이스의 이름을 사용
        return new Unit4() 
        {
            public void move() 
            {
                System.out.printf("인간형 유닛이 %d 속도로 이동합니다.\n", speed);
            }
        };    // 하나의 실행문이므로 세미콜론으로 끝납니다.
    }
}

public class Ex04_AnonymousInner2
{
    public static void main(String[] args)
    {
        HumanCamp4 hc = new HumanCamp4();
        Unit4 unit = hc.getMarine();
        unit.move();
    }
}

 

 

람다식

자바는 객체를 기반으로 프로그램을 구현함

만약 어떤 기능이 필요한데 간단한 기능이기 때문에 함수만 하나 만들어서 사용하고 싶어도, 자바는 클래스 기반의 객체지향 언어이기 때문에 간단한 클래스를 만들어줘야 함

클래스를 먼저 만들고, 클래스 안에 기능을 구현한 메서드를 만든 후 객체를 통해 그 메서드를 호출해야 함

자바는 클래스가 없으면 메서드를 사용할 수 없음

 

이런 불편함을 덜기 위해 자바 8부터는 함수형 프로그래밍 기법인 람다식(lambda expression)을 지원함

자바는 익명 내부 클래스를 람다식으로 표현해 함수형 프로그래밍을 지원함

 

익명 내부 클래스 → 람다식

interface Unit5
{
    void move(String s);
}

class Human5 implements Unit5 
{
    public void move(String s) 
    {
        System.out.println(s);
    }
}

public class Ex05_Lambda1
{
    public static void main(String[] args)
    {
        Unit5 unit = new Human5();
        unit.move("Lambda : Unit 5");
    }
}

interface Unit6
{
    void move(String s);
}

public class Ex06_Lambda2
{
    public static void main(String[] args)
    {
        Unit6 unit = new Unit6() {  // 익명 클래스
            public void move(String s) 
            {
                System.out.println(s);
            }
        };
        unit.move("Lambda : Unit 6");
    }
}

 

람다식 변환

  1. 익명 클래스를 나타내는 의미 없이 붙인 이름과 외부의 중괄호를 제거함
  2. 함수 이름, 반환형을 없애고 화살표(->) 추가
  3. 함수의 실행문 {} 블록을 남기고 문장의 끝을 알려주기 위해 세미콜론으로 마지막을 표시함
interface Unit7
{
    void move(String s);
}

public class Ex07_Lambda3
{
    public static void main(String[] args)
    {
        Unit7 unit = (String s) ->
            {
                System.out.println(s);
            };
        unit.move("Lambda : Unit 7");
    }
}

 

람다식 문법

매개변수가 하나이면 자료형과 소괄호 생략 가능

str -> {System.out.println(str);}

 

중괄호 안의 구현부가 한 문장이면 중괄호 생략 가능

str -> System.out.println(str);

 

interface Unit8
{
    void move(String s);  // 매개변수 하나, 반환형 void
}

public class Ex08_LambdaRule1
{
    public static void main(String[] args)
    {
        Unit8 unit;

        unit = (String s) -> { System.out.println(s); };
        unit.move("Lambda : 줄임 없는 표현 : 앞 예제 동일");

        unit = (String s) -> System.out.println(s);
        unit.move("Lambda : 중괄호 생략");

        unit = (s) -> System.out.println(s);
        unit.move("Lambda : 매개변수 형 생략");

        unit = s -> System.out.println(s);
        unit.move("Lambda : 매개변수 소괄호 생략");
    }
}

// 메서드 몸체가 둘 이상의 문장으로 이뤄져 있거나,
// 매개변수의 수가 둘 이상인 경우에는
// 각각 중괄호와 소괄호의 생략이 불가능합니다.

 

중괄호 안의 구현부가 한 문장이라도 return문이 있다면 중괄호 생략 불가

str -> return str.length();	// 잘못된 형식

 

매개변수가 두 개 이상이면 소괄호 생략 불가

x, y -> {System.out.println(x + y);}	// 잘못된 형식

 

중괄호 안의 구현부가 반환문 하나라면 return과 중괄호 모두 생략 가능

str -> str.length();	// 문자열의 길이를 반환함
(x, y) -> x + y;	// 두 값을 더하여 반환함

 

interface Unit9
{
    int calc(int a, int b);  // 매개변수 둘, 반환형 int
}

public class Ex09_LambdaRule2
{
    public static void main(String[] args)
    {
        Unit9 unit;
        unit = (a, b) -> { return a + b; };
        //unit = a, b -> { return a + b; }; // 앞쪽 소괄호 생략 안됨
        //unit = (a, b) -> return a + b;    // 뒤쪽 중괄호 생략 안됨
        int num = unit.calc(10, 20);
        System.out.println(num);
        
        unit = (a, b) -> a * b;  // 뒤쪽 중괄호와 return 생략 가능
        System.out.println(unit.calc(10, 20));
    }
}

// 메서드 몸체에 해당하는 내용이 return 문이면
// 그 문장이 하나이더라도 중괄호의 생략이 불가능하다.

 

매개변수가 없을 경우에는 소괄호 생략 불가

( ) -> System.out.println("Hello~");

 

interface Unit10
{
    String move();  // 매개변수 없음, 반환형 String
}

public class Ex10_LambdaRule3
{
    public static void main(String[] args)
    {
        Unit10 unit = () -> {
            return "인간형 유닛 이동";
        };

        System.out.println(unit.move());
    }
}

 

 

함수형 인터페이스

함수형 인터페이스는 람다식을 선언하는 전용 인터페이스

함수형 인터페이스는 익명 함수와 매개변수만으로 구현되므로 단 하나의 메서드만을 가져야 함

인터페이스에 @FunctionalInterface 어노테이션을 붙여서 함수형 인터페이스임을 표시해야 함

이후에 혹시라도 실수로 메서드 등을 추가하면 에러 발생

 

@FunctionalInterface가 붙어 있는 인터페이스에 만약 두 개 이상의 메서드가 있게 된다면 어떤 메서드에 익명 함수를 대입할지 모호해지기 때문에 다음과 같은 에러가 발생하게 됨

@FunctionalInterface
interface Unit11 
{
    String move();
//    void attack();
}

public class Ex11_Functional
{
    public static void main(String[] args)
    {
        Unit11 unit = () -> {
            return "인간형 유닛 이동";
        };

        System.out.println(unit.move());
    }
}