[공부 내용 정리]
java.util.function 패키지 : 함수형 인터페이스 제공
Runnable
- 매개변수와 리턴 값 모두 없는 경우
package java.lang;
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
아래 예시처럼 매개변수와 리턴값(타입)이 없는 람다식을 참조 가능함.
Runnable r = () -> System.out.println("출력문 테스트");
r.run(); // "출력문 테스트"
Supplier<T>
- 매개변수는 없고, 리턴값(타입)이 있습니다.
package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
T get();
}
아래 예시처럼 매개변수가 없고, 리턴타입이 있는 람다식을 참조 가능함.
Supplier<String> s = **() -> "리턴되는 값";**
String result = s.get();
System.out.println(result); // "리턴되는 값"
Consumer<T>
- Supplier와 반대로, 매개변수는 있지만 리턴타입이 음.
- 매개변수는 있지만 리턴타입이 없는 람다식을 참조 가능함.
package java.util.function;
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Consumer<String> c = (a) -> System.out.println(a);
c.accept("consumer");
Function<T, R>
- 하나의 매개변수를 받아서 하나의 결과를 리턴함.
- 매개변수를 받아서 하나의 결과를 리턴하는 람다식을 참조 가능함.
package java.util.function;
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Function<Integer, String> f = a -> String.valueOf(a);
Function<String, Integer> f2 = b -> {
return Integer.valueOf(b) + 100;
};
Predicate<T>
- 조건식을 표현하는데 사용됨.
- 매개변수는 하나, 리턴타입은 boolean을 갖는 함수형 인터페이스.
package java.util.function;
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
하나의 매개변수, 리턴타입이 boolean인 람다식을 참조함.
Predicate<String> isEmptyStr = s -> s.length()==0;
String str = "";
if (isEmptyStr.test(str)) { // if(s.length()==0)
System.out.println("This is an empty String.")
}
// 스트림에서 filter메소드 내부에는 Predicate 타입이 들어감
List<Integer> list = Arrays.asList(1,2,3,4,5);
list.stream()
.filter(x -> x%2==0)
.collect(Collectors.toList()); // [2,4]
문제) LongSupplier 함수형 인터페이스로 참조가 가능한
@FunctionalInterface
// Output만 존재
public interface LongSupplier {
long getAsLong();
}
LongSupplier ls =() -> {
long b = 34L;
return b;
};
System.out.println(ls.getAsLong());
메소드 참조
Method Reference : 메소드를 참조해서 매개 변수의 정보 및 리턴 타입을 알아내서, 람다식에서 불필요한 매개 변수를 제거함.
(left, right) -> Math.max(left, right);
Math::max; // 메소드 참조
-> 이 코드를 설명하면, left, right 두 개의 값을 Math.max() 메소드의 매개값으로 전달하는 역할을 함. 메소드 참조를 이용하면 이렇게 깔끔하게 처리할 수 있음!
IntBinaryOperator 인터페이스 예시)
@FunctionalInterface
public interface IntBinaryOperator {
int applyAsInt(int left, int right);
}
// Math.max 메소드
public static int max(int a, int b) {
return Math.max(a, b);
}
람다식을 '메소드참조'로 더욱 간단하게 변경해가는 과정
// 1단계
IntBinaryOperator operator = (a, b) -> Math.max(a, b);
// 2단계
IntBinaryOperator operator = Math::max; // 메소드 참조
정적 메소드 및 인스턴스 메소드 참조
정적(static) 메소드를 참조할 경우에는 클래스 이름 뒤에 :: 기호를 붙이고 정적 메소드 이름을 쓰면 됨.
클래스::메소드
인스턴스 메소드일 경우에는 먼저 객체를 생성한 다음, 참조 변수 뒤에 :: 기호를 붙이고 인스턴스 메소드 이름을 쓰면 됨.
참조변수::메소드
ex) Calculator의 정적 및 인스턴스 메소드 참조
public class MethodReferenceExample {
public static void main(String[] args) {
IntBinaryOperator operator;
// 정적 메소드 참조
operator = (x, y) -> Calculator.staticMethod(x, y);
System.out.println("결과1: " + operator.applyAsInt(1, 2));
operator = Calculator::staticMethod;
System.out.println("결과2: " + operator.applyAsInt(3, 4));
// 인스턴스 메소드 참조
Calculator calculator = new Calculator();
operator = (x, y) -> calculator.instanceMethod(x, y);
System.out.println("결과3: " + operator.applyAsInt(5, 6));
operator = calculator::instanceMethod;
System.out.println("결과4: " + operator.applyAsInt(7, 8));
}
}
public class Calculator {
public static int staticMethod(int x, int y) { // 정적 메소드
return x + y;
}
public int instanceMethod(int x, int y) { // 인스턴스 메소드
return x + y;
}
}
매개변수의 메소드 참조
람다식에서 제공되는 a 매개변수의 메소드를 호출해서 b 매개변수를 사용하는 경우
(a, b) -> { a.instanceMethod(b); }
이것을 메소드로 표현하면 a클래스 이름 뒤에 :: 기호를 붙이고, 메소드 이름을 기술하면 됨.
클래스::instanceMethod
ex) 두 문자열이 대소문자와 상관없이 동일한 알파벳으로 구성되었는지 비교하는 로직 (String의 인스턴스 메소드인 compareTolgnoreCase()) 사용.
- a.compareTolgnoreCase(b)로 호출될 때 사전 순서대로 a가 b보다 먼저 오면 음수, 동일하면 0, a가 b보다 나중에 오면 양수를 리턴함.
package chapter12;
import java.util.function.ToIntBiFunction;
public class ArgumentMethodReferencesExample {
public static void main(String[] args) {
ToIntBiFunction<String, String> function;
function = (a, b) -> a.compareToIgnoreCase(b);
print(function.applyAsInt("Java8", "JAVA8"));
function = String::compareToIgnoreCase;
print(function.applyAsInt("Java8", "JAVA8"));
}
public static void print(int order) {
if (order < 0) {
System.out.println("사전순으로 먼저 옵니다.");
} else if (order == 0) {
System.out.println("동일한 문자열");
} else {
System.out.println("사전순으로 나중에 옵니다.");
}
}
}
→ 실행 결과) 동일한 문자열, 동일한 문자열
생성자 참조
메소드 참조(method references)는 생성자 참조도 포함함. 생성자를 참조한다는 것은 객체를 생성하는 것을 의미함.
다음 코드를 보면 람다식은 단순히 객체 생성 후 리턴만 함.
(a, b) -> { return new 클래스(a, b); }
이 경우 생성자 참조로 표현하면 다음과 같음.
클래스 :: new
스트림
스트림(stream)은 데이터의 흐름이라고 할 수 있음. 자바 8부터 사용할 수 있는 기능이며 배열이나 컬렉션을 가공하여 원하는 결과를 얻을 수 있음.
// Collection 인터페이스의 메서드로 제공
Stream<E> stream()
문제) List의 숫자들 중에 짝수만 출력하는 코드
ex) 스트림을 사용하지 않았을 때
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
for (int n: numbers) {
if (n % 2 == 0) {
System.out.println(n);
}
}
ex) 스트림을 사용했을 때
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
스트림의 종류
java.util.stream 패키지에 스트림 인터페스가 정의되어있음.
스트림을 만드는 방법
1. 컬렉션으로부터 스트림 생성
문자열 리스트로 스트림을 생성하는 예시
List<String> list = Arrays.asList("a", "b", "c", "d", "e");
Stream<String> stream = list.stream();
stream.forEach(System.out::println);
2. 배열로부터 스트림 생성
String[] arr = new String[]{"a", "b", "c", "d", "e"};
Stream<String> stream = Arrays.stream(arr);
stream.forEach(System.out::println);
public class StreamExample2 {
public static void main(String[] args) {
String[] arr = {"첫번째", "두번째"};
Stream<String> stream = Arrays.stream(arr);
stream.forEach(System.out::println); // 출력
}
}
3. 숫자 범위로부터 스트림 생성
IntStream intStream = IntStream.range(1, 5); // [1, 2, 3, 4]
LongStream longStream = LongStream.rangeClosed(1, 5); // [1, 2, 3, 4, 5]
Random 클래스로 스트림을 생성할 수도 있음. 아래는 난수 5개로 스트림을 생성하는 예시임
DoubleStream doubleStream = new Random().doubles(5); // 난수 5개 생성
4. 파일로부터 스트림 생성
보류
5. 스트림 연결
Stream.concat 메소드를 이용해 두 개의 스트림을 연결할 수 있음.
Stream<Integer> stream1 = Stream.of(1, 2, 3);
Stream<Integer> stream2 = Stream.of(4, 5, 6);
Stream<Integer> newStream = Stream.concat(stream1, stream2);
newStream.forEach(System.out::println); // 1, 2, 3, 4, 5, 6
스트림 가공
distinct
중복 제거
List<String> list = Arrays.asList("a", "b", "b", "c", "d");
list.stream().distinct().forEach(System.out::println);
→ 실행 결과) a, b, c, d
filter
Predicate가 true 인 것만 필터링함. Predicate란 매개변수를 받아 boolean값을 반환하는 함수임. 즉, 조건이 참이면 true, 거짓이면 false를 리턴함.
ex) “김”으로 시작하는 문자열만 골라서 출력하는 코드
List<String> list = Arrays.asList("김밥", "김밥", "김치", "나비", "나방");
// [김밥, 김밥, 김치]
list.stream()
.filter(str -> str.startsWith("김"))
.forEach(System.out::println);
아래 예시처럼 distinct와 filter를 동시에 적용할 수도 있음!
List<String> list = Arrays.asList("김밥", "김밥", "김치", "나비", "나방");
// [김밥, 김치]
list.stream()
.filter(str -> str.startsWith("김"))
.distinct()
.forEach(System.out::println);
Daily Quiz
1. 람다식에 대한 설명으로 틀린것은?
- 람다식은 함수형 인터페이스의 익명 구현 객체를 생성한다.
- 매개 변수가 없을 경우 ( ) → { … } 형태로 작성한다.
- (x, y) → { return x + y; }는 (x, y) → x + y;로 바꿀 수 있다.
- @FunctionalInterface가 기술된 인터페이스만 람다식으로 표현이 가능하다. X
2. 메소드 참조에 대한 설명으로 틀린 것은?
- 메소드 참조는 함수적 인터페이스의 익명 구현 객체를 생성한다.
- 인스턴스 메소드는 “참조변수::메소드”로 기술한다.
- 정적 메소드는 “클래스::메소드”로 기술한다.
- 생성자 참조인 “클래스::new”는 매개 변수가 없는 디폴트 생성자만 호출한다. X → 다른 생성자도 호출할 수 있음.
3. 잘못 작성한 람다식은?
- a → a + 3
- a, b → a * b X -> 두 개 이상일 때는 앞에서 괄호를 쳐줘야 함. (a, b)
- x → System.out.println(x/5)
- (x, y) → Math.max(x, y)
4. 다음 코드는 컴파일 에러가 발생합니다. 그 이유는?
public class LambdaExample {
public static int method(int x, int y) {
IntSupplier supplier = () -> {
x *= 10;
int result = x + y;
return result;
};
int result = supplier.getAsInt();
return result;
}
public static void main(String[] args) {
System.out.println(method(3, 5));
}
}
→ 람다식 안에서는 외부 메소드의 매개변수를 final로 취급해서 변수 할당과 수정이 불가능하기 때문이다.
한 마디로, 람다식 외부의 변수는 final로 취급한다.
5. 다음은 배열 항목 중 최대값 또는 최소값을 찾는 코드입니다. maxOrMin() 메소드의 매개값을 람다식으로 기술해보세요.
public class LambdaExample_5 {
private static int[] scores = {10, 50, 3};
public static int maxOrMin(IntBinaryOperator operator) {
int result = scores[0];
for (int score : scores) {
result = operator.applyAsInt(result, score);
}
return result;
}
public static void main(String[] args) {
int max = maxOrMin(
/* 최대값 얻기 구현 */
);
System.out.println("최대값 : " + max);
int min = maxOrMin(
/* 최소값 얻기 구현 */
);
System.out.println("최소값: " + min);
}
}
최대값 : 50
최소값: 3
→ 코드 두 개 다 가능!
public static void main(String[] args) {
int max = maxOrMin(Math::max);
System.out.println("최대값 : " + max);
int min = maxOrMin(Math::min);
System.out.println("최소값: " + min);
}
public static void main(String[] args) {
int max = maxOrMin((a, b) -> a > b ? a : b);
System.out.println("최대값 : " + max);
int min = maxOrMin((a, b) -> a < b ? a : b);
System.out.println("최소값: " + min);
}
6. 다음은 학생의 영어 평균 점수와 수학 평균 점수를 계산하는 코드입니다. avg() 메소드를 선언해보세요.
public class LambdaExample_6 {
private static Student[] students = {
new Student("홍길동", 90, 96),
new Student("저팔계", 95, 93)
};
public static void main(String[] args) {
/* avg() 메소드 작성
*/
}
public static class Student {
private String name;
private int englishScore;
private int mathScore;
public Student(String name, int englishScore, int mathScore) {
this.name = name;
this.englishScore = englishScore;
this.mathScore = mathScore;
}
public String getName() {
return name;
}
public int getEnglishScore() {
return englishScore;
}
public int getMathScore() {
return mathScore;
}
}
}
→
public static void main(String[] args) {
LambdaExample_6 example = new LambdaExample_6();
double englishAvg = example.avg( s -> s.getEnglishScore() );
System.out.println("영어 평균 점수: " + englishAvg);
double mathAvg = example.avg( s -> s.getMathScore() );
System.out.println("수학 평균 점수: " + mathAvg);
}
7. 6번의 main() 메소드에서 avg() 호출할 때 매개값으로 준 람다식을 메소드 참조로 변경해보세요.
double englishAvg = average( s -> s.getEnglishScore() );
-> double englishAvg = avg ( );
double mathAvg = average( s -> s.getMathScore() );
-> double mathAvg = avg ( );
→
public static void main(String[] args) {
LambdaExample_6 example = new LambdaExample_6();
double englishAvg = example.avg(Student::getEnglishScore);
System.out.println("영어 평균 점수: " + englishAvg);
double mathAvg = example.avg(Student::getMathScore);
System.out.println("수학 평균 점수: " + mathAvg);
}
'Back-End > Java # 교육' 카테고리의 다른 글
[Java 교육] 스트림 Optional/생성/가공/결과 (다시 복습 필수) (0) | 2024.02.20 |
---|---|
[Java 교육] LIFO,FIFO 컬렉션/람다식 (다시 복습 필수 - 람다식) (1) | 2024.02.16 |
[Java 교육] Collection/List/Set/Map (0) | 2024.02.15 |
[Java 교육] 제한된 타입/와일드카드 타입/제네릭 상속, 구현 (1) | 2024.02.14 |
[Java 교육] 자동 리소스 닫기/중간정리/제네릭 (1) | 2024.02.08 |