[WarmingUp 03] 익명클래스, 람다

밍디·2024년 5월 3일

WarmingUp

목록 보기
3/7
post-thumbnail

자바의 람다식은 왜 등장했을까?
람다식과 익명 클래스는 어떤 관계가 있을까?
람다식의 문법은 어떻게 될까?


익명 클래스(Anonymous Classes)

  1. 클래스의 정의 동시에 인스턴스를 생성할 수 있다.
  2. 이름이 없다는 것을 제외하면 로컬 클래스와 같다.
  3. 클래스를 한 번만 사용하는 경우에 사용한다.

자바 문서에 있는 예시를 그냥 코드 예시로 사용하겠다.

익명 클래스는 왜 쓰지?

1. 간결성

	interface HelloWorld {
		public void greet();
		public void greetSomeone(String someone);
	}

HelloWorld 인터페이스가 있을 때

    class EnglishGreeting implements HelloWorld {
		String name = "world";
		public void greet() {
			greetSomeone("world");
		}
		public void greetSomeone(String someone) {
			name = someone;
			System.out.println("Hello " + name);
		}
	}

	HelloWorld englishGreeting = new EnglishGreeting();

보통 인터페이스를 상속 받아 인터페이스의 멤버들을 재정의하고, 객체를 생성해서 사용한다. 익명 클래스를 사용하면?


	HelloWorld frenchGreeting = new HelloWorld() {
		String name = "tout le monde";
		public void greet() {
			greetSomeone("tout le monde");
		}
		public void greetSomeone(String someone) {
			name = someone;
			System.out.println("Salut " + name);
		}
	};

객체 정의와 인스턴스 생성이 한 번에 이루어진다.
해당 클래스가 필요한 곳에서 직접 정의되어 사용되기 때문에 코드의 이해도와 가독성을 높일 수 있다.

2. 일회성
클래스가 한 번만 사용된다면, 익명 클래스는 특정 상황에서만 필요한 동작을 정의하고 사용하기 때문에 유지보수와 메모리에서 보다 효율적이다.

3. 이벤트 핸들링
GUI(그래픽 사용자 인터페이스) 애플리케이션에서 자주 사용된다.

익명 클래스 선언 위치

1. 필드
클래스의 여러 메소드에서 사용될 때

class Spanish {
    private HelloWorld spanishGreeting = new HelloWorld() {
		public void greet() {
			greetSomeone("mundo");
		}
		public void greetSomeone(String someone) {
			System.out.println("Hola " + someone);
		}
    };
    
    public void greetAll() {
        spanishGreeting.greet();
    }
    
    public void greetFriend() {
        spanishGreeting.greetSomeone("amigo~");
    }
}

2. 메서드 내부
인터페이스 구현체가 특정 메서드에서만 사용될 때

public void korean() {
    HelloWorld koreanGreeting = new HelloWorld() {
		public void greet() {
			greetSomeone("모두들");
		}
		public void greetSomeone(String someone) {
			System.out.println("안녕 " + someone);
		}
    };
    koreanGreeting.greet();
}

3. 파라미터

class Jpan {
	public void greetAll(HelloWorld japanishGreeting) {
    	japanishGreeting.greet();
	}
}

public static void main(String[] args) {
	Jpan jpan = new Jpan();
	jpan.greetAll(new HelloWorld() {
		public void greet() {
			greetSomeone("みんな");
		}
		public void greetSomeone(String someone) {
			System.out.println("こんにちは " + someone);
		}
	});
}

참고.
익명 클래스에서는 생성자를 선언할 수 없다. 이름이 없으니까!

정리.
익명 클래스는 부모 클래스의 자원을 일회성으로 이용하고 싶을 때 사용한다.

한계와 단점

1. 재사용성의 한계
익명 클래스를 다시 사용할 필요가 있는 경우, 같은 구현을 반복해서 작성해야 한다. 코드의 중복이 발생한다.

2. 가독성 저하
익명 클래스 장점에 높은 가독성이 있긴 하지만 반대로 가독성이 저하될 수 있다. (ex.익명 클래스가 긴 코드 블록을 가지고 있을 때) 따라서 코드의 가독성과 유지보수성을 저하시킬 수 있으므로 상황에 맞게 사용해야 한다.

3. 유연성의 한계
익명 클래스는 하나의 클래스나 인터페이스만 구현할 수 있다. 인터페이스의 매력이 사라진다..

위에서는 인터페이스를 상속받은 클래스 예제들만 보았는데 부모 클래스를 상속받은 익명 객체도 있다

인터페이스 익명 객체와 익명 클래스의 차이

1. 구현 방식

  • 익명 객체: 익명 객체는 인터페이스의 구현체. 인터페이스의 메서드를 오버라이드하는 것.
  • 익명 클래스: 익명 클래스는 부모 클래스를 상속받은 이름이 없는 클래스.

2. 유연성

  • 익명 객체: 해당 인터페이스에 정의된 메서드만 사용 가능.
  • 익명 클래스: 익명 클래스는 부모 클래스의 모든 멤버에 접근 가능. 새로운 멤버 추가 가능.
    - 주의! 새로 정의한 멤버는 외부에서 사용 불가

3. 재사용성

  • 익명 객체: 인터페이스의 구현체를 한 번만 만들어 사용하는 경우에 적합.
  • 익명 클래스: 익명 클래스 여러 번 사용 가능.

람다(Lambda)

람다식 도대체 어디에 쓰여?


람다 표현식을 사용하면 기능을 메서드 인수로 처리하거나 코드를 데이터로 처리할 수 있다. ??

람다는 자바 8부터 도입된 함수적 인터페이스 익명 객체를 구현하는데 사용하는 표현식이다. 즉, 람다식은 함수적 인터페이스에만 사용할 수 있다. 자바의 람다식은 코드를 간결하게 작성하고, 함수형 프로그래밍 스타일을 지원하기 위해 등장했다.

함수적 인터페이스란?

단 하나의 추상 메서드만 선언된 인터페이스.
람다식을 다루기 위한 인터페이스.

  • 람다식은 모든 인터페이스에서 사용한다(x)
    → 람다식은 하나의 메소드를 정의하기 때문에 하나의 추상 메소드만 선언된 인터페이스만 타겟이 된다.
  • 람다식은 함수적 인터페이스에서 사용한다(o)

@FunctionalInterface

컴파일러는 메소드가 몇개 선언되어있는지 확인하 메소드가 두개이상 선언되어있다면 에러가 발생한다.
생략 할 수 있으며 함수적 인터페이스라는것을 명확하게 하기 위해 사용된다.!

문법

(매개변수, ...) -> {실행문...}

인터페이스와 클래스, 사용할 메소드

interface CheckPerson {
    boolean test(Person p);
}

class CheckPersonEligibleForSelectiveService implements CheckPerson {
    public boolean test(Person p) {
        return p.gender == Person.Sex.MALE &&
            p.getAge() >= 18 &&
            p.getAge() <= 25;
    }
}

public static void printPersons(List<Person> roster, CheckPerson tester) {
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}

익명 객체를 사용하면?

CheckPerson 인스턴스를 만들고 test 메서드를 재정의 한다. 클래스를 만들 필요가 없기 때문에 필요한 코드 양은 줄어들었다. 하지만 CheckPerson 인터페이스에는 메서드가 하나만 있고, 이 경우 익명 클래스 대신 람다 식을 사용하면 더 간결하게 만들 수 있다!

printPersons(
    roster,
    new CheckPerson() {
        public boolean test(Person p) {
            return p.getGender() == Person.Sex.MALE
                && p.getAge() >= 18
                && p.getAge() <= 25;
        }
    }
);

람다식을 사용하면?

  1. new, 메서드명 제거하고 블록{ }앞에 ‘→’를 추가한다.
printPersons(
    roster,
    (Person p) -> { p.getGender() == Person.Sex.MALE
                  && p.getAge() >= 18
                  && p.getAge() <= 25 
        		}; 
);
  1. 변수 타입은 자동으로 인식하기 때문에 생략할 수 있다.
printPersons(
    roster,
    (p) -> { p.getGender() == Person.Sex.MALE
                  && p.getAge() >= 18
                  && p.getAge() <= 25 
        	}; 
);
  1. 변수가 하나면 괄호( )를 생략할 수 있다. 중괄호{ }에 return 문만 있거나 문장이 하나면 중괄호를 생략 할 수 있다.
printPersons(
    roster,
    p -> p.getGender() == Person.Sex.MALE
                  && p.getAge() >= 18
                  && p.getAge() <= 25 
);

// 다른 예시
btn.setOnAction(
           event -> System.out.println("Hello World!") 
);

코드가 굉장히 간결해졌다.

단점은?

1. 가독성 감소
람다식이 복잡할 경우 코드의 의도를 파악하기 어려울 수 있다.

2. 디버깅의 어려움
람다식은 익명으로 정의되기 때문에 디버깅이 어려울 수 있다.

3. 성능
람다식을 사용할 경우 해당 코드가 실행될 때마다 인스턴스가 생성하고 초기화되기 대문에 이에 따른 오버헤드가 발생할 수 있다.


참고
https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#syntax
https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
https://inpa.tistory.com/entry/%E2%98%95-Lambda-Expression

0개의 댓글