# 익명클래스 ## 익명클래스란? - 이름이 없는 클래스 - 한번만 사용되며, 클래스를 선언하고 동시에 인스턴스화 할 때 사용된다. - 주 GUI 구성 요소를 정의하거나, 간단한 인터페이스 구현에 사용되며, 코드를 더욱 간단하게 만들수 있도록 한다 ## 익명클래스를 사용하는 이유 - 간결성: 익명 클래스는 주로 한 곳에서만 사용되며, 따라서 별도의 클래스 파일을 작성하는 것에 비해 코드를 간결하게 만들 수 있다. - 지역적인 사용: 익명 클래스는 메소드 내부에서 정의하고 사용할 수 있어, 그 사용 범위를 한정할 수 있다. 이는 코드의 가독성과 유지 보수성을 향상시킨다. - 코드의 캡슐화: 익명 클래스는 그것을 사용하는 코드에 가깝게 위치할 수 있어, 특정 동작을 캡슐화하는데 유용하다. 예를 들어, 버튼 클릭 리스너와 같은 GUI 이벤트 처리 코드에서 종종 볼 수 있다. ## 익명클래스 사용 시 주의사항 - 재사용성 부족: 익명 클래스는 이름이 없기 때문에 재사용할 수 없다. 따라서, 같은 코드를 반복해서 사용해야 하는 경우에는 적합하지 않다. - 복잡성: 비교적 단순한 동작을 가진 클래스를 정의하는 경우에는 익명 클래스가 유용할 수 있지만, 너무 많은 동작을 가진 클래스를 익명 클래스로 만드는 것은 코드를 이해하고 유지 보수하는데 어려움을 초래할 수 있다. - 구문 제한: 익명 클래스에서는 일부 구문 (예: 생성자, static 멤버 등)을 사용할 수 없다. 또한, 익명 클래스는 자신을 둘러싼 블록의 final 또는 effectively final 변수에만 접근할 수 있다. - 가독성: 익명 클래스의 사용은 코드의 가독성을 떨어뜨릴 수 있다. 특히, 여러 개의 중첩된 익명 클래스는 코드를 읽고 이해하는데 어려움을 줄 수 있다. ## 익명클래스 구현 방법 - 익명클래스는 클래스 선언과 객체의 생성을 동시에 수행한다. - 주로 `new` 키워드 뒤에 인터페이스나 클래스 이름을 명시하고, 그 뒤에 중괄호 `{}`를 추가하여 클래스 본문을 작성한다. - 중괄호 안에는 해당 인터피에스나 클래스를 오버라이딩하는 메서드를 포함시켜 익명클래스를 완성시킨다 ## 익명클래스 예시 ### 예시 1 #### 익명 클래스 사용 전 ``` java interface HelloWorld { public void greet(); } class EnglishGreeting implements HelloWorld { public void greet() { System.out.println("Hello world!"); } } class Main { public static void main(String[] args) { HelloWorld englishGreeting = new EnglishGreeting(); englishGreeting.greet(); } } ``` #### 익명 클래스 사용 후 ``` java interface HelloWorld { public void greet(); } class Main { public static void main(String[] args) { HelloWorld englishGreeting = new HelloWorld() { public void greet() { System.out.println("Hello world!"); } }; englishGreeting.greet(); } } ``` ### 예시 2 #### Runnable 인터페이스의 익명 클래스 사용 예 ``` java Runnable runnable = new Runnable() { @Override public void run() { System.out.println("익명 클래스의 run 메소드"); } }; Thread thread = new Thread(runnable); thread.start(); ``` > 위의 코드에서, `new Runnable() { ... }` 부분이 Runnable 인터페이스를 구현하는 익명 클래스이다. Runnable 인터페이스의 run 메소드를 오버라이드하여 구현하고 있다. #### Thread의 익명 클래스 사용 예 ``` java Thread thread = new Thread() { @Override public void run() { System.out.println("익명 클래스의 run 메소드"); } }; thread.start(); ``` > Thread 클래스를 상속받는 익명 클래스. 이 클래스도 마찬가지로 run 메소드를 오버라이드하여 구현하고 있다. # 함수형 프로그래밍이란? ![](https://hackmd.io/_uploads/HkXF-dlPn.png) - 함수형 프로그래밍(Functional Programming, FP)은 컴퓨터 프로그래밍의 패러다임 중 하나 - 함수의 평가를 프로그래밍의 주요 방법으로 사용하는 접근 방식을 가르킨다. - 수학적 함수의 개념을 프로그래밍에 적용하여 부작용(Side Effects)을 최소화하고 높은 수준의 추상화를 제공한다. 함수형 프로그래밍의 주요 특징은 다음과 같다 - **불변성(Immutability)**: 함수형 프로그래밍에서는 데이터가 한 번 생성된 후에는 그 상태가 변하지 않는다. 이는 복잡성을 줄이고 버그를 예방하는 데 도움이 된다. - **순수 함수(Pure Functions)**: 순수 함수는 같은 입력이 주어지면 항상 같은 출력을 반환하며, 프로그램의 상태를 변경하거나 부작용을 유발하지 않는다. - **고차 함수(Higher-order Functions)**: 함수형 프로그래밍에서는 함수를 다른 함수의 인수로 전달하거나, 함수에서 함수를 반환할 수 있다다. - **재귀(Recursion)**: 함수형 프로그래밍 언어는 상태 변경을 피하려고 루프(Loop) 사용을 최소화하고, 대신 재귀를 사용하여 반복적인 작업을 수행한다. - **형식 추론(Type Inference)**: 많은 함수형 프로그래밍 언어는 형식을 명시적으로 선언하지 않아도 프로그램의 형식을 추론할 수 있다. - **지연 평가(Lazy Evaluation)**: 함수형 프로그래밍 언어는 필요할 때까지 계산을 미루는 지연 평가를 지원하는 경우가 많다. 이렇게 하면 무한 시퀀스를 모델링하거나, 효율성을 높이는 데 도움이 될 수 있다. 함수형 프로그래밍은 복잡성을 관리하고, 가독성과 유지 보수성을 향상시키며, 동시성과 병렬성을 처리하기 위한 효율적인 도구로 간주된다. 그러나 이러한 이점을 최대화하려면 함수형 프로그래밍의 원칙과 패턴을 정확히 이해하고 적용해야 한다. # 자바 함수형 인터페이스 ## 함수형 인터페이스란? - 함수형 인터페이스는 자바에서 함수형 프로그래밍을 지원하기 위해 도입된 개념. - 자바는 원래 객체 지향 프로그래밍(OOP) 언어로, 모든 동작은 객체와 그 객체의 메서드로 이루어진다는 기본 원칙을 따르고 있지만, Java 8부터 함수형 프로그래밍의 개념이 도입되면서 "함수형 인터페이스(Functional Interface)"라는 개념이 등장했다. - 자바의 함수형 인터페이스는 함수형 프로그래밍의 기반을 제공하며, 함수형 프로그래밍의 원칙과 개념을 자바로 구현할 수 있도록 도와준다. - 이를 통해 자바 개발자들도 함수형 프로그래밍의 이점을 활용하여 좀 더 유연하고 효율적인 코드를 작성할 수 있게 되었다. ## 함수형 인터페이스의 이해 - 함수형 인터페이스는 '정확히 하나의 추상 메서드를 가진 인터페이스'를 의미한다. 물론, `default` 메서드나 `static` 메서드는 여러 개 있을 수 있다. - 이러한 함수형 인터페이스는 람다 표현식을 위한 타입으로 사용되며, 이를 통해 메서드를 직접 참조하거나 전달할 수 있게 된다. - 함수형 인터페이스는 `@FunctionalInterface` 어노테이션으로 명시할 수 있다. - 이 어노테이션은 인터페이스가 함수형 인터페이스의 규칙을 따르는지 컴파일러에게 확인하도록 요청하는 역할을 한다. ```java //구현해야 할 메소드가 한개이므로 Functional Interface이다. @FunctionalInterface public interface Math { public int Calc(int first, int second); } //구현해야 할 메소드가 두개이므로 Functional Interface가 아니다. (오류 사항) @FunctionalInterface public interface Math { public int Calc(int first, int second); public int Calc2(int first, int second); } ``` ## 함수형 인터페이스의 예 자바는 여러 가지 내장 함수형 인터페이스를 제공하고 있다. `java.util.function` 패키지에서 이들을 확인할 수 있다. 이 중 몇 가지를 살펴보면 다음과 같다 ![](https://hackmd.io/_uploads/SJxm8plqUh.png) ## 함수형 인터페이스 사용 예시 - Predicate 인터페이스 예시 ```java import java.util.function.Predicate; public class Main { public static void main(String[] args) { Predicate<Integer> isEven = new Predicate<Integer>() { @Override public boolean test(Integer n) { return n % 2 == 0; } }; System.out.println(isEven.test(4)); // 출력: true System.out.println(isEven.test(5)); // 출력: false } } ``` - Function 인터페이스 예시 ```java import java.util.function.Function; public class Main { public static void main(String[] args) { Function<Integer, String> intToString = new Function<Integer, String>() { @Override public String apply(Integer n) { return Integer.toString(n); } }; System.out.println(intToString.apply(12345)); // 출력: "12345" } } ``` - Supplier 인터페이스 예시 ```java import java.util.function.Supplier; public class Main { public static void main(String[] args) { Supplier<Double> randomSupplier = new Supplier<Double>() { @Override public Double get() { return Math.random(); } }; System.out.println(randomSupplier.get()); // 임의의 랜덤값 출력 } } ``` - Consumer 인터페이스 예시 ```java import java.util.function.Consumer; public class Main { public static void main(String[] args) { Consumer<String> print = new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }; print.accept("Hello, world!"); // 출력: "Hello, world!" } } ``` # 자바에서의 람다 - 람다 표현식(Lambda expression)은 익명 함수(이름이 없는 함수)를 정의하는 방법이다. - 람다는 프로그램 코드에서 한 번이나 두 번 사용되고 버려질 수 있는 작은 코드 조각을 간단하게 표현하기 위해 주로 사용된다. - 람다 표현식은 Java 8에서 처음 도입된 기능으로, 간결하게 함수를 표현하는 방법이다. - 함수형 인터페이스와 결합하여 사용되며, 이를 통해 더욱 간결하고 직관적인 코드 작성이 가능하다. 람다 표현식의 기본 구조는 다음과 같다 ```java (parameter) -> { body } ``` - `parameters`: 함수가 받는 인자들의 목록. 이 부분은 함수가 인자를 받지 않는 경우 생략될 수 있다. - `->`: 람다 표현식의 인자 목록과 본문을 분리하는 토큰. - `body`: 함수의 본문으로, 실행될 코드 블록을 의미한다. ## 람다와 함수형 인터페이스의 연관성 - 함수형 인터페이스의 인스턴스를 생성하는 방법 중 하나 - 함수형 인터페이스의 유일한 추상 메서드와 람다 표현식이 실행할 코드 사이에는 일대일 대응 관계가 있다. 예를 들어, 다음과 같은 함수형 인터페이스가 있다고 가정해보자. ```java @FunctionalInterface public interface SimpleFunctionalInterface { void doWork(); } ``` 이 함수형 인터페이스의 인스턴스를 람다 표현식을 사용하여 생성할 수 있다. ```java SimpleFunctionalInterface sfi = () -> System.out.println("Doing work..."); ``` 이렇게 람다 표현식을 사용하면, 구현해야 하는 메서드의 시그니처를 이미 알고 있는 함수형 인터페이스에 대해 훨씬 간결하게 코드를 작성할 수 있다. ## 람다의 표현식 - 매개변수 화살표 `->` 함수몸체로 이용하여 사용할 수 있다 - 매개변수가 하나일 경우 매개변수 생략 가능하다 - 함수몸체가 단일 실행문이면 괄호 `{}`생략 가능하다 - 함수몸체가 return문으로만 구성되어있는 경우 괄호 `{}` 생략 가능하다 ### 람다의 표현식 예시 - `() -> {}`: 아무런 파라미터를 받지 않고 아무런 동작도 하지 않는 람다 표현식. - `() -> 1`: 아무런 파라미터를 받지 않고 항상 1을 반환하는 람다 표현식. - `() -> { return 1; }`: 위와 동일하지만 명시적으로 return 키워드를 사용하고 있다. - `(int x) -> x+1`: 정수 x를 입력으로 받아 x+1을 반환하는 람다 표현식. - `(x) -> x+1`, `x -> x+1`: 위와 동일하지만 타입을 명시하지 않았다. 자바의 타입 추론 기능 덕분에 가능하다. - `(int x) -> { return x+1; }`, `x -> { return x+1; }`: 위와 동일하지만 중괄호와 return 키워드를 사용하고 있다. - `(int x, int y) -> x+y`, `(x, y) -> x+y`, `(x, y) -> { return x+y; }`: 두 개의 정수를 입력으로 받아 두 수의 합을 반환하는 람다 표현식. - `(String lam) -> lam.length()`, `lam -> lam.length()`: 문자열을 입력으로 받아 문자열의 길이를 반환하는 람다 표현식. - `(Thread lamT) -> { lamT.start(); }`, `lamT -> { lamT.start(); }`: Thread 객체를 입력으로 받아 스레드를 시작하는 람다 표현식. > 잘못된 람다의 표현식 > `(x, int y) -> x+y` : 람다 표현식에서는 모든 파라미터의 타입을 명시하거나, 모든 파라미터의 타입을 생략해야 한다. > `(x, final y) -> x+y` : 람다 파라미터는 기본적으로 final이므로 final 키워드를 사용할 수 없다. ## 람다의 특징 - 익명 함수: 람다식은 익명 함수로써 이름이 없고, 함수 자체가 값으로 취급된다. 이를 통해 필요한 곳에서 즉석으로 함수를 정의하고 사용할 수 있다. - 함수형 인터페이스와 함께 사용: 람다식은 주로 함수형 인터페이스를 구현하기 위해 사용된다. 함수형 인터페이스는 하나의 추상 메서드를 가진 인터페이스로, 람다식을 통해 해당 추상 메서드를 구현할 수 있다. - 클로저: 람다식은 클로저(closure) 개념을 지원한다. 클로저는 함수 내부에서 외부 변수에 접근할 수 있는 개념으로, 람다식을 포함하는 범위 내의 변수에 접근할 수 있다. 이를 통해 함수 내에서 외부 변수를 사용하면서 불변성과 상태 변경을 조절할 수 있다. ## 람다의 장단점 ### 장점 - 간결성과 가독성: 람다식은 코드의 길이를 줄여주고 가독성을 향상시켜 준다. 필요한 내용에 집중할 수 있으며, 불필요한 부분을 제거하여 코드를 간결하게 작성할 수 있다. - 함수형 프로그래밍 지원: 람다식은 함수형 프로그래밍 패러다임을 지원한다. 함수를 값으로 다루는 것이 가능하며, 불변성과 순수성을 강조하는 함수형 프로그래밍의 특징을 구현할 수 있다. - 유연한 사용: 람다식을 변수에 할당하거나 매개변수로 전달하는 등의 유연한 사용이 가능하다. 이를 통해 코드의 재사용성과 확장성을 높일 수 있다. - 병렬 처리와 비동기 프로그래밍: 람다식은 병렬 처리와 비동기 프로그래밍에 유용하게 사용될 수 있다. 병렬 스트림, CompletableFuture 등과 함께 사용하여 병렬 처리 작업을 간편하게 구현할 수 있다. ### 단점 - 학습 곡선: 람다식의 문법과 개념은 초기에는 생소할 수 있다. 함수형 프로그래밍 패러다임에 익숙하지 않은 개발자들은 학습 곡선을 거쳐야 할 수 있다. - 제약사항: 람다식은 함수형 인터페이스에만 사용될 수 있다. 따라서, 이미 정의된 인터페이스에 람다식을 사용하기 위해서는 해당 인터페이스가 함수형 인터페이스여야 한다. - 디버깅의 어려움: 람다식은 익명 함수로서 이름이 없기 때문에 디버깅 시점에서 추적이 어려울 수 있다. 따라서, 디버깅에 어려움을 겪을 수 있다. - 성능 오버헤드: 람다식을 사용하면 메모리 사용과 실행 시간 측면에서 약간의 오버헤드가 발생할 수 있다. 하지만 대부분의 상황에서는 미미한 차이로 인해 실제로는 큰 문제가 되지 않는다. ## 람다 식 사용 예시 - Predicate 인터페이스 예시 ```java import java.util.function.Predicate; public class Main { public static void main(String[] args) { Predicate<Integer> isEven = n -> n % 2 == 0; System.out.println(isEven.test(4)); // 출력: true System.out.println(isEven.test(5)); // 출력: false } } ``` - Function 인터페이스 예시 ```java import java.util.function.Function; public class Main { public static void main(String[] args) { Function<Integer, String> intToString = n -> Integer.toString(n); System.out.println(intToString.apply(12345)); // 출력: "12345" } } ``` - Supplier 인터페이스 예시 ```java import java.util.function.Supplier; public class Main { public static void main(String[] args) { Supplier<Double> randomSupplier = () -> Math.random(); System.out.println(randomSupplier.get()); // 임의의 랜덤값 출력 } } ``` - Consumer 인터페이스 예시 ```java import java.util.function.Consumer; public class Main { public static void main(String[] args) { Consumer<String> print = s -> System.out.println(s); print.accept("Hello, world!"); // 출력: "Hello, world!" } } ``` --- ###### tags: `과외(하희영)`