자바에서는 함수를 값처럼 직접 전달할 수 없습니다.
즉, exe("hello", func)와 같은 형태는 지원되지 않으며,
반드시 메서드 실행 결과 (func())를 인자로 전달해야 합니다.
그러나 람다 표현식과 함수형 인터페이스를 사용하면 동작(메서드)을 변수처럼 다룰 수 있으며, 인자로 전달하는 것이 가능합니다.
이를 통해 유지보수성과 확장성을 높일 수 있습니다.
람다 표현식(Lambda Expression)은 메서드처럼 기능을 전달하는 방식을 제공하여 코드를 간결하게 만들고, 함수형 프로그래밍 스타일을 지원합니다. 이를 활용하면 특정 동작을 하나의 식으로 표현할 수 있습니다.
람다 표현식은 다음과 같은 형식으로 작성됩니다.
(parameter1, parameter2, ...) -> { body }
Arrays.sort()의 두 번째 인자로 람다 표현식을 전달하여 문자열의 길이를 기준으로 정렬합니다.
package lambda;
import java.util.Arrays;
public class LambdaExample {
public static void main(String[] args) {
// 정렬할 문자열 배열
String[] names = {"Alice", "Bob", "Charlie", "David"};
// 람다 표현식을 사용하여 문자열 길이순으로 정렬
Arrays.sort(names, (a, b) -> a.length() - b.length());
// 정렬된 배열 출력
for (String name : names) {
System.out.println(name);
}
}
}
Arrays.sort(names, (a, b) -> a.length() - b.length());a.length() - b.length() → 문자열 길이 차이를 반환하여 정렬람다식을 사용하려면 함수형 인터페이스(FunctionalInterface)가 필요합니다.
함수형 인터페이스는 추상 메서드가 하나만 존재하는 인터페이스입니다.
package lambda;
@FunctionalInterface
interface MyFunction {
void performAction(String message); // 단 하나의 추상 메서드
}
@FunctionalInterface → 컴파일러가 함수형 인터페이스임을 보장performAction(String message); → 람다식에서 구현할 메서드람다식을 사용하여 MyFunction을 구현하고 특정 동작을 실행합니다.
package lambda;
public class LambdaExample2 {
// 함수형 인터페이스를 인자로 받는 메서드
public static void executeAction(String message, MyFunction function) {
function.performAction(message);
}
public static void main(String[] args) {
// 람다식을 사용하여 함수형 인터페이스 구현
MyFunction myFunction = (message) -> System.out.println("Action performed: " + message);
// 함수형 인터페이스를 인자로 갖는 메서드 호출
executeAction("Hello, world!", myFunction);
}
}
executeAction(String message, MyFunction function) → 함수형 인터페이스를 매개변수로 받음myFunction = (message) -> System.out.println("Action performed: " + message); → 람다식 구현executeAction("Hello, world!", myFunction); → 람다식을 메서드에 전달하여 실행Action performed: Hello, world!
자바에서는 함수형 인터페이스를 활용하여 람다식을 사용하고, 이를 인자로 전달할 수 있습니다.
package lambda;
@FunctionalInterface //- 함수형 인터페이스이므로, 람다식을 사용할 수 있습니다. 하나만의 메서드만 만들수 있음
interface MyFunction {
void performAction(String message); //메서드 1개
//void a(int b); 이거 하면 오류남 람다식에는 메서드가 1개여야하는데 위에 일단 1개로 강제하기도 했서
}
@FunctionalInterface를 사용하여 함수형 인터페이스임을 명시performAction(String message);는 람다식에서 구현할 메서드package lambda;
public class LambdaExample2 {
// 함수형 인터페이스를 인자로 받는 메서드 정의
public static void executeAction(String message, MyFunction function) {
function.performAction(message);
}
public static void main(String[] args) {
// 람다식을 사용하여 함수형 인터페이스 구현
MyFunction myFunction = (message) -> System.out.println("Action performed: " + message);
// 함수형 인터페이스를 인자로 갖는 메서드 호출
executeAction("Hello, world!", myFunction);
}
}
출력 결과
Action performed: Hello, world!
executeAction(String message, MyFunction function) → 함수형 인터페이스를 인자로 받음myFunction에 람다식을 사용하여 메서드 구현executeAction()에서 함수형 인터페이스를 실행자바에서 제공하는 기본 함수형 인터페이스를 사용하면 다양한 형태의 함수 표현이 가능합니다.
| 인터페이스 | 설명 | 메서드 | 예제 |
|---|---|---|---|
| Supplier | 매개변수 없이 결과 값을 제공 | T get() | 데이터 소스로부터 값을 제공 |
| Consumer | 매개변수를 받아서 소비 | void accept(T t) | 리스트의 각 요소 출력 |
| Function<T, R> | 하나의 인자를 받아 결과 반환 | R apply(T t) | 문자열을 정수로 변환 |
| Predicate | 주어진 조건을 평가 | boolean test(T t) | 리스트에서 특정 조건을 만족하는 요소 필터링 |
| UnaryOperator | 하나의 인자를 받아 같은 유형의 결과 반환 | T apply(T t) | 숫자를 제곱하는 등의 동작 |
| BinaryOperator | 두 개의 인자를 받아 같은 유형의 결과 반환 | T apply(T t1, T t2) | 두 숫자를 더하거나 두 문자열을 연결 |
package lambda;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
/**
* MyUtil 클래스
* 이 클래스는 제네릭 타입 <T>를 사용하여 다양한 데이터 유형을 처리할 수 있음.
* filter() 메서드는 주어진 조건(Predicate 인터페이스)을 충족하는 요소만 리스트에 포함하여 반환함.
*/
public class MyUtil<T> { // 제네릭 타입 <T>를 지정한 클래스 선언
/**
* filter() 메서드
* @param objects 필터링할 대상 리스트 (List<T>)
* @param pred 필터링 조건을 정의하는 함수형 인터페이스 Predicate<T>
* @return 조건을 만족하는 요소만 포함한 새로운 리스트 반환 (List<T>)
*/
public List<T> filter(List<T> objects, Predicate<T> pred){
List<T> output = new ArrayList<>(); // 필터링된 결과를 저장할 리스트 생성
// 리스트의 모든 요소에 대해 반복
for(T obj : objects) {
// Predicate<T>.test(obj) 호출 -> 주어진 조건을 만족하는지 검사
if(pred.test(obj)) {
output.add(obj); // 조건을 만족하면 output 리스트에 추가
}
}
return output; // 필터링된 리스트 반환
}
}
package lambda;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class LambdaExample0 {
public static void main(String[] args) {
// Step 1: 정수 리스트 생성 (Arrays.asList 사용)
List<Integer> intList = Arrays.asList(10, 15, 23, 15, 3125, 2);
// Step 2: MyUtil<Integer> 객체 생성 (제네릭 활용)
MyUtil<Integer> util = new MyUtil<>(); // MyUtil 클래스의 제네릭 인스턴스 생성
// Step 3: 람다 표현식을 이용하여 필터링 (짝수만 포함)
List<Integer> filteredOutput = util.filter(intList, (data) -> data % 2 == 0);
System.out.println(filteredOutput); // 출력: [10, 2]
// Step 4: 익명 클래스를 이용하여 필터링 (짝수만 포함)
List<Integer> filteredOutput2 = util.filter(intList, new Predicate<Integer>() {
@Override
public boolean test(Integer data) {
return data % 2 == 0; // 짝수만 반환하는 조건
}
});
System.out.println(filteredOutput2); // 출력: [10, 2]
// Step 5: 문자열 리스트 생성
List<String> strList = Arrays.asList("안녕", "ㅁㄴㅇ", "ㅂㅈㅎㅂㅈㄹ", "ㅍㄴㅇㅍ");
// Step 6: MyUtil<String> 객체 생성 (제네릭 활용)
MyUtil<String> util2 = new MyUtil<>();
// Step 7: 람다 표현식을 이용하여 필터링 (문자 길이가 5 이상인 경우)
List<String> filteredOutput3 = util2.filter(strList, (data) -> data.length() >= 5);
System.out.println(filteredOutput3); // 출력: ["ㅂㅈㅎㅂㅈㄹ"]
}
}
람다 표현식을 더욱 간결하게 작성할 수 있도록 메서드 참조 연산자(::)를 사용하면 기존 메서드나 생성자를 직접 참조할 수 있습니다.
클래스명::메서드명package lambda;
import java.util.function.Function;
public class MethodReferenceExample1 {
// 정적 메서드
public static String toUpperCase(String str) {
return str.toUpperCase();
}
public static void main(String[] args) {
// 람다 표현식을 사용한 방식
Function<String, String> lambda = (str) -> toUpperCase(str);
// 메서드 참조를 사용한 방식
Function<String, String> reference = MethodReferenceExample1::toUpperCase;
System.out.println(lambda.apply("hello")); // 출력: HELLO
System.out.println(reference.apply("world")); // 출력: WORLD
}
}
Function<String, String>은 입력값을 받아 변환 후 반환하는 함수형 인터페이스apply("hello")는 Function에서 제공하는 메서드로, 값을 전달하여 함수 실행객체변수명::메서드명package lambda;
import java.util.function.Function;
public class MethodReferenceExample2 {
// 인스턴스 메서드
public String toUpperCase(String str) {
return str.toUpperCase();
}
public static void main(String[] args) {
MethodReferenceExample2 instance = new MethodReferenceExample2();
// 람다 표현식을 사용한 방식
Function<String, String> lambda = (str) -> instance.toUpperCase(str);
// 메서드 참조를 사용한 방식
Function<String, String> reference = instance::toUpperCase;
System.out.println(lambda.apply("hello")); // 출력: HELLO
System.out.println(reference.apply("world")); // 출력: WORLD
}
}
apply("hello")는 Function에서 람다식이나 메서드 참조를 통해 값을 변환instance::toUpperCase를 사용하면 기존 객체의 메서드를 참조하여 실행클래스명::newpackage lambda;
import java.util.function.Supplier;
public class MethodReferenceExample3 {
// 생성자
public MethodReferenceExample3() {
System.out.println("Constructor called");
}
public static void main(String[] args) {
// 람다 표현식을 사용한 방식
Supplier<MethodReferenceExample3> lambda = () -> new MethodReferenceExample3();
// 생성자 참조를 사용한 방식
Supplier<MethodReferenceExample3> reference = MethodReferenceExample3::new;
lambda.get(); // 생성자 호출
reference.get(); // 생성자 호출
}
}
Supplier<MethodReferenceExample3>은 매개변수가 없는 객체를 반환하는 함수형 인터페이스get()은 Supplier에서 제공하는 메서드로, 객체를 생성하여 반환| 메서드명 | 사용 인터페이스 | 기능 |
|---|---|---|
| apply() | Function<T, R> | 입력값(T)을 받아 변환 후 반환(R) |
| get() | Supplier<T> | 입력값 없이 객체를 반환 |
apply()는 입력값을 받지만, get()은 입력값 없이 호출 가능하다는 차이가 있습니다.
자바의 스트림(Stream) API는 컬렉션(리스트, 셋, 맵 등)과 배열 같은 데이터 소스를 효율적으로 처리하는 기능을 제공합니다. 이를 활용하면 가독성이 높은 코드 작성이 가능하며, 선언적 스타일로 데이터를 다룰 수 있습니다.
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
Stream<String> stream = Stream.<String>builder()
.add("a")
.add("b")
.add("c")
.build();
Stream<String> stream = Stream.of("a", "b", "c");
중간 연산은 스트림을 변환하며, 최종 연산이 호출되기 전까지 실제로 실행되지 않습니다.
stream.filter(s -> s.startsWith("a"));
stream.map(String::toUpperCase);
List<List<String>> list = Arrays.asList(
Arrays.asList("a"), Arrays.asList("b", "c"), Arrays.asList("d")
);
Stream<String> flatStream = list.stream().flatMap(Collection::stream);
stream.distinct();
stream.sorted();
stream.peek(System.out::println);
n개 요소 포함stream.limit(2);
n개 요소 제외stream.skip(1);
최종 연산은 스트림을 소비하며, 값을 반환하거나 컬렉션을 생성합니다.
stream.forEach(System.out::println);
List<String> resultList = stream.collect(Collectors.toList());
Optional<String> concatenated = stream.reduce((s1, s2) -> s1 + s2);
long count = stream.count();
boolean anyStartsWithA = stream.anyMatch(s -> s.startsWith("a"));
boolean allStartWithA = stream.allMatch(s -> s.startsWith("a"));
boolean noneStartWithA = stream.noneMatch(s -> s.startsWith("a"));
Optional<String> first = stream.findFirst();
Optional<String> any = stream.findAny();
이 클래스는 기본적인 스트림 연산을 수행하는 예제입니다. 여기서 순회, 정렬, 필터링, 매핑, 집계, 그룹핑을 적용합니다.
package stream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class StreamOperationExample {
public static void main(String[] args) {
// 리스트 생성 (1~10)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 1. 순회 (각 요소 출력)
System.out.println("순회:");
numbers.stream().forEach(System.out::println);
// 2. 정렬 (오름차순)
System.out.println("\n정렬:");
numbers.stream().sorted().forEach(System.out::println);
// 3. 필터링 (짝수만 출력)
System.out.println("\n짝수 필터링:");
numbers.stream().filter(n -> n % 2 == 0).forEach(System.out::println);
// 4. 매핑 (각 숫자의 제곱값으로 변환)
System.out.println("\n제곱 값 매핑:");
numbers.stream().map(n -> n * n).forEach(System.out::println);
// 5. 집계 (합계 계산)
System.out.println("\n합계:");
int sum = numbers.stream().reduce(0, Integer::sum);
System.out.println(sum);
// 6. 그룹핑 (3으로 나눈 나머지 기준으로 그룹화)
System.out.println("\n나머지에 따른 그룹핑:");
Map<Integer, List<Integer>> groupByRemainder = numbers.stream()
.collect(Collectors.groupingBy(n -> n % 3));
System.out.println(groupByRemainder);
}
}
forEach): 모든 요소를 하나씩 반복하며 출력.sorted): 스트림의 요소를 정렬 (기본 오름차순).filter): 조건을 만족하는 요소만 포함.map): 각 요소를 새로운 값으로 변환.reduce): 요소를 하나의 값으로 합침.groupingBy): 특정 기준으로 요소를 그룹화.학생 정보를 저장하는 간단한 데이터 클래스입니다.
package stream;
public class Student {
private String name;
private int score;
// 생성자 (이름과 점수를 초기화)
public Student(String name, int score) {
this.name = name;
this.score = score;
}
// 이름을 반환하는 메서드
public String getName() {
return name;
}
// 점수를 반환하는 메서드
public int getScore() {
return score;
}
}
name과 score를 멤버 변수로 가짐.getName, getScore)**를 통해 필드 값을 반환.이 클래스는 학생 데이터를 스트림을 이용해 처리하는 예제입니다.
package stream;
import java.util.Arrays;
import java.util.List;
import java.util.OptionalDouble;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
// 학생 리스트 생성
List<Student> students = Arrays.asList(
new Student("Alice", 85),
new Student("Bob", 92),
new Student("Charlie", 78),
new Student("Dave", 88),
new Student("Eve", 95)
);
// 1. 평균 점수 계산
OptionalDouble averageScore = students.stream()
.mapToInt(Student::getScore)
.average();
averageScore.ifPresent(avg -> System.out.println("Average score: " + avg));
// 2. 90점 이상인 학생들의 이름 추출
List<String> topStudents = students.stream()
.filter(s -> s.getScore() >= 90) // 조건: 90점 이상
.map(Student::getName) // 이름으로 변환
.collect(Collectors.toList()); // 리스트로 수집
System.out.println("Top students: " + topStudents);
}
}