16. [Java] Annotation, 람다식, 스트림 / 20230503

2023. 5. 8. 21:43

🧑🏻‍💻 TIL(Today I Learned)


🧑🏻‍💻 Annotation, 람다식, 스트림

 

1. 애너테이션(Annotation)

➡️ 소스 코드가 컴파일되거나 실행될 때 컴파일러 및 다른 프로그램에게 필요한 정보를 전달 해주는 문법

➡️ 주석은 사람, 즉 사용자에게 정보를 전달한다면 애너테이션은 컴파일러나 다른 프로그램에게 정보 전달 

  • @ 로 시작하며 클래스, 인터페이스, 필드, 메서드 등에 붙여서 사용가능
  • 위 사진의 @Override는 example()이 추상 메서드를 구현하거나 상위 클래스의 메서드를 오버라이딩 한 메서드라는 것을 컴파일에게 알려주는 역할 

 

🔎  애너테이션의 종류 

➡️ 기본적으로 JDK에서 제공하는 애너테이션도 있지만 다른 프로그램에서 제공하는 것도 있음

➡️ 구분 

  • 표준 애너테이션 
    : JDK에 내장된 일반적인 애너테이션 → 위 예시와 같이 다른 문법 요소에 붙여서 사용하는 것이 일반적
  • 메타 애너테이션 
    : 다른 애너테이션을 정의할 때 사용하는 애너테이션
    → 애너테이션을 직접 정의해서 사용할 때 사용하는 애너테이션
    → 애너테이션을 사용자가 직접 정의해서 사용가능 (사용자 정의 애너테이션)

✍🏻  표준 애너테이션

  • @Override
    ➡️ 메서드 앞에서만 붙일 수 있는 애너테이션
    ➡️ 선언한 메서드가 상위 클래스의 메서드를 오버라이딩하거나 추상 메서드를 구현하는 메서드라는 것을 컴파일러에게 알려주는 역할
class SuperClass {
    public void example() { // 같은 이름을 가진 메서드가 상위 클래스에 있는지 확인 (2)
        System.out.println("example() of SuperClass");
    }
}

class SubClass extends SuperClass {

    @Override // 컴파일 과정에서 발견 (1)
    public void example() {
        System.out.println("example() of SubClass");
    }
}
📍 상위 클래스에 같은 이름의 메서드가 존재하는지 확인하는 이유? 
: 어떤 메서드를 오버라이딩하거나 구현할 때 오타가 날 경우 @Override 를 붙여주지 않으면 컴파일러는 그냥 다른 이름의 새로운 메서드를 정의한다고 간주하고 에러를 발생시키지 않음
→ 결국 에러가 났을 때 어디서 에러가 났는지 원인을 찾기 어려움 

 

  • @Deprecated(사용이 중단된, 더이상 권장하지 않는)
    ➡️ 기존에 사용하던 기술이 다른 기술로 대체되어 기존 기술을 적용한 코드를 더 이상 사용하지 않도록 유도하는 경우 사용 

➡️ 클래스를 만들어 @Deprecated 붙여준 뒤 인스턴스화하고 getOldField() 호출하면 취소선이 그어지면서 나타남과 함께 is deprected 라고 경고 메세지 출력

➡️ 직접 컴파일하고 확인해 보면 위와 같은 경고 메세지가 나타남

"Deprected" 로 표시된 메서드나 클래스 사용하거나 오버라이드하는 코드를 발견했다는 뜻

➡️ 기존의 코드를 다른 코드와의 호환성 문제로 삭제하기 곤란해 남겨두어야 하지만 더 이상 사용하는 것을 권장하지 않을 때 사용

 

  • @SupperessWarnings
    ➡️ 컴파일 경고 메세지가 나타나지 않도록 함 
    ➡️ 경고가 발생할 것을 예상하고 있으면서 묵인해야할 때 사용 
@SuppressWarnings("all") -- 모든 경고 억제

@SuppressWarnings("deprecation") 

@SuppressWarnings({"deprecation", "unused", "null"})


// 위와 같이 괄호를 붙이고 그 안에 억제하고자 하는 경고 메시지 지정
// 하나씩 넣어도 되고 여러 개 넣어줄 수 있음 
// 특정 내용과 관련된 경고 메시지 선택적으로 억제가능

 

@FuntionallInterface

➡️ 함수형 인터페이스를 선언할 때 컴파일러가 함수형 인터페이스의 선언이 바르게 되었는지 확인하도록 함, 바르게 선언되지 않으면 에러!

@FunctionalInterface
public interface ExampleInterface {
	public abstract void example(); // 단 하나의 추상 메서드
}

// 함수형 인터페이스는 단 하나의 추상 메서드를 가져야 함! 
// 위와 같이 잘 작성되었는지 컴파일러에게 검사할 것을 요구함

 

 

🔎 메타 애너테이션

➡️ 애너테이션을 정의하는 데에 사용되는 애너테이션

➡️ 애너테이션의 적용 대상 및 유지 기간을 지정하는 데 사용 

@Override의 소스코드

  • 애너테이션을 정의할 때는 @interface 키워드 사용
  • @Target, @Retention 은 @Override의 적용 대상과 유지 기간을 지정하는 역할 

 

@Target

➡️ 애너테이션을 적용할 대상을 지정하는 데 사용

➡️ 아래 표가 나와 있는 내용이 애너테이션을 사용하여 지정할 수 있는 대상의 타입

import static java.lang.annotation.ElementType.*; 
//import문을 이용하여 ElementType.TYPE 대신 TYPE과 같이 간단히 작성할 수 있음

@Target({FIELD, TYPE, TYPE_USE})	// 적용대상이 FIELD, TYPE
public @interface CustomAnnotation { }	// CustomAnnotation을 정의

@CustomAnnotation	// 적용대상이 TYPE인 경우
class Main {
    
@CustomAnnotation	// 적용대상이 FIELD인 경우
    int i;
}

 

 

@Inherited

➡️ 하위 클래스가 애너테이션을 상속받도록 함

➡️ 상위 클래스에 붙이면 하위 클래스도 상위 클래스에 붙은 애너테이션이 동일하게 적용 

@Inherited // @SuperAnnotation이 하위 클래스까지 적용
@interface SuperAnnotation{ }

@SuperAnnotation
class Super { }

class Sub extends Super{ } // Sub에 애너테이션이 붙은 것으로 인식

 

 

@Retention

➡️ 특정 애너테이션의 지속 시간 결정하는 데 사용

➡️ 유지 정책(Retention Policy) : 애너테이션이 유지되는 기간을 정하는 속성 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE) 
//오버라이딩이 제대로 되었는지 컴파일러가 확인하는 용도 
//클래스 파일에 남길 필요 없이 컴파일 시에만 확인하고 사라짐 --> 실행 시에는 더이상 사용되지 않음
public @interface Override(){ }

 

 

@Repeatable

➡️ 애너테이션을 여러 번 붙일 수 있도록 허용한다는 의미 

@Repeatable(Works.class) // Work 애너테이션을 여러 번 반복해서 쓸 수 있게 한다.  
@interface Work{  // 사용자 타입의 애너테이션 정의 
    String value();  
}

@Work("코드 업데이트")  
@Work("메서드 오버라이딩")  
class Main{  
	... 생략 ...
}

@interface Works {  // 여러 개의 Work애너테이션을 담을 컨테이너 애너테이션 Works
    Work[] value(); 
}

@Repeatable(Works.class) // 컨테이너 애너테이션 지정 
@interface Work {
	String value();
}

// 일반적인 애너테이션과는 달리 같은 이름의 애너테이션이 여러 번 적용될 수 있기 때문에 이 애너테이션들을 묶어주는
// 별도의 애너테이션 작성해야함

 

 

🔎 사용자 정의 애너테이션

➡️ 사용자가 직접 애너테이션을 정의해서 사용하는 것 

@interface 애너테이션명 { // 인터페이스 앞에 @기호만 붙이면 애너테이션을 정의할 수 있습니다. 
	타입 요소명(); // 애너테이션 요소를 선언
}

➡️ 애너테이션은 다른 클래스나 인터페이스를 상속받을 수 없음 

 

 

2. 람다(Lamda)

➡️ 람다식(Lamda Expression)은 함수형 프로그래밍 기법을 지원하는 자바의 문법요소

➡️ 메서드를 하나의 식으로 표현하여 코드를 매우 간결하면서 명확하게 표현 가능

➡️ 메서드를 더 간단하고 편리하게 표현하기 위해 고안된 문법 요소 

 

🔎 기본 문법 

//기존 메서드 표현 방식
void sayhello() {
	System.out.println("HELLO!")
}

//위의 코드를 람다식으로 표현한 식
() -> System.out.println("HELLO!")

// 반환 타입과 메서드 이름 생략 가능
//--> 익명 함수(anonymous function) 부르기도 함
// 기존 방식
int sum(int num1, int num2) {
	return num1 + num2;
}

// 람다식
(int num1, int num2) -> {
	num1 + num2
}

// 특정 조건이 충족되면 람다식을 더욱 축약하여 표현 가능 
// 메서드 바디에 실행문이 하나만 존재할 경우 중괄호와 return 문 생략가능, 세미콜론까지!
(int num1, int num2) -> num1 + num2
// 매개변수 타입을 함수형 인터페이스를 통해 유추할 수 있는 경우 매개변수 타입 생략가능 
(num1, num2) -> num1 + num2

 

 

🔎 함수형 인터페이스

➡️ 자바에서는 함수는 반드시 클래스 안에서 정의되어야 하므로 메서드가 독립적으로 있을 수 없고 반드시 클래스 객체를 먼저 생성한 후 생성한 객체로 메서드를 호출해야 함 
➡️ 즉 람다식 또한 객체, 이름이 없기 때문에 익명 객체라고 할 수 있음 

익명 객체는 익명 클래스를 통해 만들 수 있는데 익명 클래스란 객체의 선언과 생성을 동시에 하여 오직 하나의 객체를 생성하고 단 한 번만 사용되는 일회용 클래스 
// sum 메서드 람다식
(num1, num2) -> num1 + num2

// 람다식을 객체로 표현 --> 생성과 선언을 동시에 할 수 있음
new Object() {
	int sum(int num1, int num2) {
		return num1 + num1;
	}
}

  • 익명 객체를 생성하여 참조 변수 obj에 담아준다고 하더라도 sum 메서드를 사용할 수 있는 방법이 없음 
    자바의 함수형 인터페이스(Functional Interface) 사용하여 해결할 수 있음
  • 함수형 인터페이스(Functional Interface)는 단 하나의 추상 메서드만 선언가능
    → 람다식과 인터페이스의 메서드가 1:1로 매칭되어야 하기 때문에

함수형 인터페이스 사용 예시

  • 함수형 인터페이스인 ExampleFunction에 추상 메서드 sum() 정의됨
    → 람다식을 참조할 참조 변수를 선언할 때 타입으로 사용하기 위해 필요함 
  • 함수형 인터페이스를 사용하면 참조 변수의 타입으로 함수형 인터페이스 사용하여 원하는 메서드에 접근가능

 

✍🏻 매개변수와 리턴값이 없는 람다식

@FunctionalInterface
interface MyFunctionalInterface {
    void accept(); // 매개변수와 리턴값이 없는 추상 메서드를 가진 함수형 인터페이스
}

public class MyFunctionalInterfaceExample {
    public static void main(String[] args) throws Exception {
				// accept()가 매개변수를 가지지 않으니 람다식도 가지지 않음 
        MyFunctionalInterface example = () -> System.out.println("accept() 호출");
        example.accept(); // 메서드 호출 --> {} 실행
    }
}

// 출력값
accept() 호출

 

 

✍🏻  매개변수가 있는 람다식

@FunctionalInterface
public interface MyFunctionalInterface {
    void accept(int x);
}
// 매개변수가 있고 리턴값이 없는 추상메서드를 가진 함수형 인터페이스


public class MyFunctionalInterfaceExample {

    public static void main(String[] args) throws Exception {

        MyFunctionalInterface example;
				// accept()가 매개변수를 하나 가지기 때문에 람다식도 하나 가진다
        example = (x) -> {
            int result = x * 5;
            System.out.println(result);
        };
        example.accept(2);

        example = (x) -> System.out.println(x * 5);
        example.accept(2);
    }
}

// 출력값
10
10

 

 

✍🏻 리턴값이 있는 람다식

 

 

🔎 메서드 레퍼런스(참조)

➡️ 람다식에서 불필요한 매개변수를 제거할 때 주로 사용

➡️ 더욱더 간단하게 사용하기 위한 방법!

 

  • 정적 메서드와 인스턴스 메서드 참조
클래스 :: 메서드
// 클래스 이름 뒤에 :: 기호 붙이고 정적 메서드 이름 기술


참조 변수 :: 메서드
// 객체를 먼저 생성한 다음 참조 변수 뒤에 :: 기호 붙이고 인스턴스 메서드 이름 기술
  • 생성자 참조
    ➡️ 생성자를 참조한다는 것은 객체 생성을 의미 
    ➡️ 단순히 객체를 생성하고 리턴하도록 구성된 람다식은 생성자 참조로 대치가능
    ➡️ 생성자가 오버로딩되어 여러 개 있으면 컴파일러는 함수형 인터페이스의 추상 메서드와 동일한 매개 변수 타입과 개수가 있는 생성자를 찾아 실행, 만약 생성자가 존재하지 않으면 컴파일 오류 
(a,b) -> new 클래스(a,b)

//생성자 참조 문법

클래스 :: new

  • 생성자 참조는 두 가지 방법 모두 같지만 실행되는 Member 생성자가 다름 

내용이 많고 중요해서 길어졌다...... 제발 블로그 미루지 말자! 2탄으로 계속......

BELATED ARTICLES

more