Java8에서 소개된 Lamda 표현식과 Stream API는 JAVA 언어를 더욱 강력하게 만들어 주었습니다. 코드를 간결하게 작성할 수 있도록 도와주며, 컬렉션 처리, 필터링, 매핑, 리듀싱 등 다양한 연산을 지원하여 코드의 가독성과 유지보수성을 향상시켜줍니다. 아래에서 Lamda 표현식과 Stream API 대한 설명과 예시를 이어 나가도록 하겠습니다.
Lamda 표현식
람다 표현식은 익명 함수를 간단하게 표현하는 방법으로 자바8부터 도입 되었습니다. 간결한 문법으로 함수형 프로그래밍의 이점을 활용할 수 있습니다.
간결한 문법
// 기존의 방식
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Hello, World!");
}
};
// 람다 표현식
Runnable runnableLambda = () -> System.out.println("Hello, World!");
Function<T,R> 내용 및 사용 방법
자바에서 제공하는 함수형 인터페이스로, 하나의 입력 값<T>을 받아서 결과값<R> 을 반환하는 함수입니다. 이 인터페이스는 apply 메서드를 가지고 있어, 구현체에서는 입력값을 받아서 결과 값을 반환하는 로직을 구현합니다.
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
// T : 입력 타입
// R : 현재 Function 이 반환하는 결과 타입
// V : 체인된 후속 Function이 반환하는 결과 타입
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after)
데이터 변환
public class FunctionExample {
public static void main(String[] args) {
// 문자열을 정수로 변환하는 Function
Function<String, Integer> stringToInt = s -> Integer.parseInt(s);
// 정수를 이진수 문자열로 변환하는 Function
Function<Integer, String> intToBinary = i -> Integer.toBinaryString(i);
// 데이터 변환 연결
// andThen -> 두 함수를 체인으로 연결하여 사용
String binaryResult = stringToInt.andThen(intToBinary).apply("10");
System.out.println(binaryResult); // 출력: 1010
}
}
데이터 매핑
아래 코드에서 map 함수는 Function 인터페이스를 활용하여 각 요소를 변환하고 반환하는 데에 사용됩니다. map 메서드는 스트림의 각 요소에 대해 주어진 함수를 적용하고, 그 결과를 새로운 스트림으로 반환합니다.
public class FunctionExample {
// 2. 사용 방법 (데이터 매핑)
public static void main(String[] args) {
List<String> words = List.of("apple", "banana", "orange");
Function<String, String> toUpperCase = String::toUpperCase;
List<String> collect = words.stream()
.map(word -> toUpperCase.apply(word))
.collect(Collectors.toList());
System.out.println("collect = " + collect);
}
}
이해를 돕기 위해 Function<String, String> toUpperCase = String::toUpperCase; 응 사용하여 map에 적용하였는데요, map 함수는 Function 인터페이스를 활용하기 때문에 위 처럼 코드 하실 필요 없이 아래와 같이 바로 적용 하면 됩니다.
List<String> words = List.of("apple", "banana", "orange");
List<String> collect = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
조건에 따른 처리
public class FunctionExample {
public static void main(String[] args) {
Function<String, String> lengthClassifier = s -> s.length() < 5 ? "Short" : "Long";
String result1 = lengthClassifier.apply("Java");
String result2 = lengthClassifier.apply("JavaScript");
System.out.println("result1 = " + result1);
System.out.println("result2 = " + result2);
}
}
아래 처럼 표현할 수 있습니다
List<String> words = List.of("apple", "banana", "orange", "cat");
List<String> collect = words.stream()
.map(s -> s.length() < 5 ? "Short" : "Long")
.map(s -> s.toUpperCase())
.collect(Collectors.toList());
System.out.println("collect = " + collect);
Supplier<T> 내용 및 사용 방법
Supplier<T>는 자바에서 제공하는 함수형 인터페이스 중 하나로, 이 인터페이스는 파라미터를 받지 않고 값을 제공만 하는 역할을 합니다. 즉, get() 메서드를 사용하여 값을 제공하는 함수를 나타냅니다.
@FunctionalInterface
public interface Supplier<T> {
T get();
}
지연로딩
public class SupplierExample1 {
public static void main(String[] args) {
SupplierExample1 example = new SupplierExample1();
System.out.println("example = " + example.getResult());
}
private String getResult() {
// 필요한 시점에 비용이 큰 연산 실행
return expensiveOperation.get();
}
private Supplier<String> expensiveOperation = () -> {
// 비용이 큰 연산 또는 리소스 로딩
return "Result of expensive operation";
};
}
값의 캐싱 및 기타
public class SupplierExample {
public static void main(String[] args) {
SupplierExample2 example = new SupplierExample2();
Supplier<String> value = () -> { return "Hello World";};
System.out.println("example.getCacheValue() = " + example.getCacheValue());
System.out.println("example.getCacheValue() = " + example.getCacheValue());
System.out.println("example = " + value.get());
Supplier<LocalDateTime> dateTime = LocalDateTime::now;
System.out.println("dateTime = " + dateTime.get());
}
private String getCacheValue() {
// 필요한 시점에 캐싱된 값 반환
return cachedValue.get();
}
private Supplier<String> cachedValue = () -> {
// 데이터베이스 또는다른 비용이 큰 연산
return "Cached value";
};
}
랜덤 값 생성
public class SupplierExample {
Supplier<Integer> randomValue = () -> (int) (Math.random() * 100);
private static void printRandomValue(Supplier<Integer> supplier, int count) {
for ( int i=0; i<count; i++) {
System.out.println(supplier.get());
}
}
public static void main(String[] args) {
SupplierExample3 example = new SupplierExample3();
printRandomValue(example.randomValue, 5);
}
}
stream에서는 주로 generate 메서드와 조합하여 사용됩니다. 아래는 stream.generate를 활용한 예시 입니다.
public class SupplierExample {
public static void main(String[] args) {
Supplier<Integer> randomValue = () -> (int) (Math.random() * 100);
Stream.generate(randomValue)
.limit(10)
.forEach(System.out::println);
}
}
public class SupplierExample {
public static void main(String[] args) {
Supplier<Integer> randomValue = () -> (int) (Math.random() * 100);
Stream.generate(() -> Math.random() * 100)
.map(aDouble -> aDouble.intValue())
.filter(integer -> integer % 2 == 0)
.limit(10)
.forEach(System.out::println);
}
}
Consumer<T> 내용 및 사용 방법
Consumer는 입력값을 받아서 소비하는 역할을 합니다. 이 인터페이스는 매개변수를 받는 accept 메서드를 정의하고 있습니다.
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
사용 방법
아래 코드에서 Consumer을 이용하면 실제 printConsumer을 건드리지 않고 Consumer을 생성하여 다양한 output를 만들어 낼 수 있습니다.
public class ConsumerExample {
public static void main(String[] args) {
// 문자열을 출력하는 Consumer
Consumer<String> printString = s -> System.out.println(s);
printString.accept("Hello World");
// 다양한 구성을 할 수 있는 consumer
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
Consumer<Integer> printInteger = i -> System.out.println("different " + i);
printConsumer(numbers, printInteger);
}
private static <T> void printConsumer(List<T> inputs, Consumer<T> consumer) {
for (T input : inputs) {
consumer.accept(input);
}
}
}
consumer를 스트림과 함께 사용하는 경우는 주로 forEach 메서드와 결합하여 각 요소를 소비하는 작업을 수행할때 발생합니다. 아래는 간단한 예시로, 리스트의 각 요소를 출력하는 Consumer을 스트림과 함께 사용 하는 예제 입니다.
public class ConsumerExample2 {
public static void main(String[] args) {
List<String> words = List.of("apple", "banana", "orange");
words.stream().forEach(s -> System.out.println(s));
}
}
Predicate<T> 내용 및 사용 방법
Predicate는 주어진 입력값에 대해 조건을 검사하고 true or false를 반환하는 역할을 합니다. test 메소드를 사용하여 입력값을 평가 합니다.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
사용 방법
Predicate는 주로 컬렉션의 요소를 조건에 맞게 필터링 하는데 사용됩니다. 예를 들어, 리스트에서 짝수만 추출하거나 특정 조건을 만족하는 요소만 선택하는 등의 작업에 많이 활용됩니다.
public class PredicateExample {
public static void main(String[] args) {
Predicate<Integer> isPositive = integer -> integer % 2 == 0;
System.out.println(isPositive.test(1)); // 출력: false
System.out.println(isPositive.test(2)); // 출력: true
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
List<Integer> result = numbers.stream()
.filter(isPositive)
.collect(Collectors.toList());
System.out.println("result = " + result);
}
}
'자바' 카테고리의 다른 글
Stream API (0) | 2024.02.02 |
---|---|
Lamda Method Reference (0) | 2024.01.31 |
함수형 프로그래밍과 일급객체 (0) | 2024.01.31 |
내부 클래스 ( inner class ) 란? (0) | 2024.01.02 |
자바 캐스팅(casting) (1) | 2023.12.15 |