수정이력
2022.04.16. 글 표현 다듬기. 불필요한 내용 삭제
람다식이란
람다식은 익명함수(anonymous function)로 구동된다. 자바 8버전부터 적용 가능하다. 람다식은 마치 함수처럼 작성하지만, 실행시 익명구현 객체를 생성하는 방식으로 구동된다. 람다식은 병렬처리, 이벤트 처리 등 함수적 프로그래밍에서 유용하게 쓰인다.
람다식을 쓰는 이유
- 람다식을 쓰지 않을 때보다 코드가 간결해진다 (기호 및 약속된 표현을 쓰기 때문에)
- 필터링 및 매핑시 집계결과를 쉽게 가져올 수 있다 (내장된 메서드를 사용할 수 있음)
함수적 인터페이스
함수적 인터페이스(functional interface)는 추상메서드가 1개만 정의된 인터페이스이다. 람다식은 함수적 인터페이스에 사용할 수 있다. 람다식을 실행하면 인터페이스를 구현하는 객체가 생성된다. 람다식은 이름을 따로 지정하지 않으므로 익명구현객체가 생성된다.
인터페이스에는 추상메서드가 있고, 이를 구현해야 메서드로써 사용할 수 있다. 메서드가 1개라면 당연히 해당 메서드가 구현하려는 메서드일 것이다. 하지만 메서드가 여러개라면 람다식으로 표현했을때 어떤 메서드를 실행해야 하는지 컴파일러가 알 수 없을 것이다.
함수적 인터페이스는 @FunctionalInterface 애노테이션을 붙일 수 있다. @FunctionalInterface 애노테이션은 추상메서드가 1개만 선언되도록 표시하는 것이다. 이 어노테이션이 있으면 인터페이스에 메서드가 2개 이상 선언되면 컴파일 오류가 발생한다. 애노테이션은 선택사항이다.
@FunctionalInterface
요약
- 람다식을 실행하면 익명구현객체가 생성된다
- 람다식은 모든 인터페이스에서 사용한다(x)
- 람다식은 함수적 인터페이스에서 사용한다(o)
람다식의 기본형태
A a = (매개값) -> { 구현코드 };
*example
Runnable 인터페이스를 구현해보자. 참고로 Runnable 인터페이스는 run() 메서드 1개만 갖고 있으므로 람다식 적용이 가능하다.
람다식을 적용하지 않는다면 아래와 같이 코딩한다.
Runnable 인터페이스를 구현하는 코드 (람다식 적용x)
Runnable runnable = new Runnable(){
public void run(){
System.out.println("Lambda");
}
}
람다식을 적용하면 아래와 같이 코딩한다.
Runnable 인터페이스를 구현하는 람다식코드
Runnable runnable = () -> {
System.out.println("Lambda");
};
람다식 작성
람다식은 매개변수와 리턴값의 유무에 따라 아래와 같이 구분된다. 코드 작성방법을 알아보자.
- 매개변수가 없고, 리턴값이 없는 람다식
- 매개변수가 있고, 리턴값이 없는 람다식
- 매개변수가 없고, 리턴값이 있는 람다식
- 매개변수가 있고, 리턴값이 있는 람다식
1) 매개변수가 없고, 리턴값이 없는 람다식
람다식 테스트를 위해서는 함수적 인터페이스가 필요하다. JavaCoding이라는 인터페이스에 nowCoding() 메서드를 선언했다.
@FunctionalInterface
public interface JavaCoding {
void nowCoding();
}
public class Execute {
public static void main(String[] args) {
//객체 선언
JavaCoding jc;
//{} 실행코드 뒤에 세미콜론(;)을 붙여야한다
jc = () -> {
System.out.println("Rollin' Rollin' Rollin' Rollin'");
};
jc.nowCoding();
// {} 실행코드가 1줄인경우 {} 생략가능
jc = () -> System.out.println("Rollin' Rollin' Rollin' Rollin'");
jc.nowCoding();
}
}
실행 결과
Rollin' Rollin' Rollin' Rollin' Rollin' Rollin' Rollin' Rollin' |
2) 매개변수가 있고, 리턴값이 없는 람다식
@FunctionalInterface
public interface JavaCoding {
void nowCoding(String str);
}
public class Execute {
public static void main(String[] args) {
//객체 선언
JavaCoding jc;
String str;
jc = (a) -> {
System.out.println(a+ " Rolling in the deep");
};
str = "하루가 멀다하고";
jc.nowCoding(str);
//람다식 바디{}를 생략하고 한 줄에 작성하기
jc = (a) -> System.out.println(a+ " Babe just only you");
str= "기다리고 있잖아";
jc.nowCoding(str);
//매개변수가 1개인 경우 () 생략할 수 있음
jc = a -> System.out.println(a+ " 기다리고 있어요");
jc.nowCoding("온종일 난 그대 생각에");
}
}
실행 결과
하루가 멀다하고 Rolling in the deep 기다리고 있잖아 Babe just only you 온종일 난 그대 생각에 기다리고 있어요 |
3) 매개변수가 없고, 리턴값이 있는 람다식
@FunctionalInterface
public interface JavaCoding {
String nowCoding();
}
public class Execute {
public static void main(String[] args) {
//객체 선언
JavaCoding jc;
String str1 = "그 날을 잊지 못해 baby";
String str2 = "날 보며 환히 웃던 너의 미소에";
String str3 = "홀린 듯 I'm fall in love";
jc = () -> {
return str1;
};
System.out.println(jc.nowCoding());
jc = () -> { return str2; };
System.out.println(jc.nowCoding());
//실행코드가 return 만 있는 경우 {}와 return문 생략가능
jc = () -> str3;
System.out.println(jc.nowCoding());
}
}
실행 결과
그 날을 잊지 못해 baby 날 보며 환히 웃던 너의 미소에 홀린 듯 I'm fall in love |
4) 매개변수가 있고, 리턴값이 있는 람다식
@FunctionalInterface
public interface JavaCoding {
String nowCoding(String s);
}
public class Execute {
public static void main(String[] args) {
//객체 선언
JavaCoding jc;
String str1 = " 너의 생각뿐이야";
String str2 = " 미치겠어";
String str3 = " 보고 싶어";
jc = (s) -> {
return s+ str1;
};
System.out.println(jc.nowCoding("온통"));
jc = (s) -> { return s+ str2; };
System.out.println(jc.nowCoding("나도"));
jc = s -> s+ str3;
System.out.println(jc.nowCoding("너무"));
}
}
실행 결과
온통 너의 생각뿐이야 나도 미치겠어 너무 보고 싶어 |
표준 API의 함수적 인터페이스
java.util.function 표준 API 패키지의 함수적 인터페이스를 사용해보자.
종류 | 매개값 | 리턴값 | 메서드형태 |
활용 |
Consumer | O | X | accept | |
Supplier | X | O | get... | |
Function | O | O | apply... | 매개값을 매핑(=타입변환)해서 리턴하기 |
Operator | O | O | apply... | 매개값을 연산해서 결과 리턴하기 |
Predicate | O | O | test | 매개값이 조건에 맞는지 확인해서 boolean 리턴 |
1) Consumer 함수적 인터페이스
- 역할 : 소비자. 매개값이 있고, 리턴값 없다
- 메서드 : accept()
인터페이스 형태 | 내용 |
XXXConsumer<T> | XXX 형태의 인자값을 받는다 |
BiConsumer<T, U> | T, U 형태의 인자값 2개를 받는다 |
ObjXXXConsumer<T> | T, XXX 형태의 인자값 2개를 받는다 |
아래 코드를 보자
public class ConsumerTest {
Consumer<String> c1 = a -> System.out.println("입력값 : "+ a);
BiConsumer<String, Integer> c2 = (a, b) -> System.out.println("입력값1 : "+ a+ " 입력값2 : "+ b);
IntConsumer c3 = a -> System.out.println("입력값 : "+ a);
DoubleConsumer c4 = a -> System.out.println("입력값 : "+ a);
LongConsumer c5 = a -> System.out.println("입력값 : "+ a);
ObjIntConsumer<Student> c6 = (a, b) -> System.out.println("이름 : "+ a.name+ " 숫자 : "+ b);
ObjDoubleConsumer<Student> c7 = (a, b) -> System.out.println("이름 : "+ a.name+ " 숫자 : "+ b);
ObjLongConsumer<Student> c8 = (a, b) -> System.out.println("이름 : "+ a.name+ " 숫자 : "+ b);
class Student{
private String name;
Student(String name){this.name = name;};
}
public static void main(String[] args) {
ConsumerTest test = new ConsumerTest();
test.c1.accept("김유신");
test.c2.accept("홍길동", 123);
test.c3.accept(456);
test.c4.accept(123.45);
test.c5.accept(1232345);
Student student = test.new Student("이순신");
test.c6.accept(student, 12);
test.c7.accept(student, 23.24);
test.c8.accept(student, 98765);
}
}
실행 결과
입력값 : 김유신 입력값1 : 홍길동 입력값2 : 123 입력값 : 456 입력값 : 123.45 입력값 : 1232345 이름 : 이순신 숫자 : 12 이름 : 이순신 숫자 : 23.24 이름 : 이순신 숫자 : 98765 |
2) Supplier 함수적 인터페이스
- 역할 : 생산자. 매개값이 없고, 리턴값이 있다
- 메서드 : getXXX()
인터페이스 형태 | 내용 |
Supplier<T> | T형 반환 |
XXXSupplier | XXX형 반환 |
아래 코드를 보자
public class SupplierTest {
public static void main(String[] args) {
String stringValue = "helloWorld";
boolean booleanValue = true;
double doubleValue = 13245.123;
int intValue = 123;
long longValue = 1234567;
Supplier<String> supplier = () -> stringValue;
BooleanSupplier booleanSup = () -> booleanValue;
DoubleSupplier doubleSup = () -> doubleValue;
LongSupplier longSup = () -> longValue;
IntSupplier intSup = () -> intValue;
SupplierTest test = new SupplierTest();
String s = supplier.get();
System.out.println("String 값 : "+ s);
boolean b = booleanSup.getAsBoolean();
System.out.println("boolean 값 : "+ b);
double d = doubleSup.getAsDouble();
System.out.println("double 값 : "+ d);
long l = longSup.getAsLong();
System.out.println("long 값 : "+ l);
int i = intSup.getAsInt();
System.out.println("int 값 : "+ i);
}
}
실행 결과
String 값 : helloWorld boolean 값 : true double 값 : 13245.123 long 값 : 1234567 int 값 : 123 |
3) Function 함수적 인터페이스
- 역할 : 매핑(타입변환)하기
- 메서드 : applyXXX()
- 예를들어 List 항목중에서 int 값을 추출하거나 다른 타입으로 변환하는 등의 작업에 사용한다
- 앞서 실행한 함수적 인터페이스의 결과값이 다음 함수적 인터페이스의 인자값으로 할당되어 최종값을 도출한다.
인터페이스 형태 | 내용 |
Function<T, U> | T 받아서 U 리턴 |
BiFunction<T, U, R> | T, U 형태를 받아서 R 리턴 |
XXXFunction<T> | XXX 받아서 T 리턴 |
XXXtoYYYFunction | XXX 받아서 YYY 리턴 |
toXXXFunction<T> | T 받아서 XXX 리턴 |
toXXXBiFunction<T, U> | T, U 받아서 XXX 리턴 |
아래 코드를 보자
public class Test01 {
class Student{
private int stuNum;
private String stuName;
private int math;
private int english;
Student(int stuNum, String stuName, int math, int english){
this.stuNum = stuNum;
this.stuName = stuName;
this.math = math;
this.english = english;
}
}
public static void main(String[] args) {
Test01 test = new Test01();
Student st1 = test.new Student(123, "홍길동", 11, 36);
//매핑 : Student객체 - Student의 Integer 값
Function<Student, Integer> function = a -> a.stuNum;
int result01 = function.apply(st1);
System.out.println("홍길동 번호 : "+ result01);
//매핑 : 두 Integer 값 - Double 값
BiFunction<Integer, Integer, Double> biFunction = (a, b) -> (double) (a+b)/2;
double result02 = biFunction.apply(3, 5);
System.out.println("두 숫자 평균 : "+ result02);
//매핑 : Double 값 - Integer 값
DoubleFunction<Integer> doubleFunction = a -> {
Double d = Math.floor(a);
return d.intValue();
};
int result03 = doubleFunction.apply(246.71);
System.out.println("소수점 버리기 : "+ result03);
//매핑 : Integer, Integer - Double
ToDoubleBiFunction<Integer, Integer> toDoubleBiFunction;
toDoubleBiFunction = (math, english) -> (double)(math+english)/2;
double result04 = toDoubleBiFunction.applyAsDouble(st1.math, st1.english);
System.out.println("홍길동의 수학 영어 평균 : "+ result04);
}
}
실행 결과
홍길동 번호 : 123 두 숫자 평균 : 4.0 소수점 버리기 : 246 홍길동의 수학 영어 평균 : 23.5 |
이번에는 난이도를 조금 높여보자
List 에 Student 객체를 요소로 담는다. ToIntFunction 을 활용해 특정과목의 점수를 가져오도록 매핑 설정을 한다. 이후 메서드를 이용해 원하는 과목의 점수를 가져오도록 메서드도 구현했다.
public class Test02 {
class Student{
private int stuNum;
private String stuName;
private int math;
private int english;
Student(int stuNum, String stuName, int math, int english){
this.stuNum = stuNum;
this.stuName = stuName;
this.math = math;
this.english = english;
}
}
public static void main(String[] args) {
Test02 test = new Test02();
List<Student> list = Arrays.asList(
test.new Student(11, "조성모", 27, 22),
test.new Student(13, "성시경", 37, 26),
test.new Student(14, "이승철", 36, 81),
test.new Student(15, "이수영", 37, 29)
);
//매핑 : Student - 수학점수
ToIntFunction<Student> toIntFunction_math = student -> student.math;
//매핑 : Student - 영어점수
ToIntFunction<Student> toIntFunction_english = student -> student.english;
System.out.println("--수학점수 가져오기");
for(Student stu : list){
int math = test.getScore(toIntFunction_math, stu);
System.out.print(math+ " ");
}
System.out.println("\n--영어점수 가져오기");
for(Student stu : list){
int english = test.getScore(toIntFunction_english, stu);
System.out.print(english+ " ");
}
}
//메서드 : function에 해당하는 과목 점수를 반환하기
public int getScore(ToIntFunction toIntFunction, Student student){
int score = toIntFunction.applyAsInt(student);
return score;
}
}
실행 결과
--수학점수 가져오기 27 37 36 37 --영어점수 가져오기 22 26 81 29 |
평균 점수도 구해보자
각 학생의 수학, 영어 평균점수와 모든학생 평균점수를 구해보자
public class Test03 {
class Student{
private int stuNum;
private String stuName;
private int math;
private int english;
Student(int stuNum, String stuName, int math, int english){
this.stuNum = stuNum;
this.stuName = stuName;
this.math = math;
this.english = english;
}
public int getMath(){
return math;
}
public int getEnglish(){
return english;
}
}
public static void main(String[] args) {
Test03 test = new Test03();
List<Student> list = Arrays.asList(
test.new Student(11, "조성모", 27, 22),
test.new Student(13, "성시경", 37, 26),
test.new Student(14, "이승철", 36, 81),
test.new Student(15, "이수영", 37, 29)
);
//매핑 : 수학, 영어 --> 평균점수
ToDoubleFunction<Student> toDoubleFunction =
student -> (double)(student.getMath() + student.getEnglish())/2;
//메서드에 값 넣기
test.testT(toDoubleFunction, list);
}
void testT(ToDoubleFunction function, List<Student> list){
double sum = 0;
for(Student stu : list){
double score = function.applyAsDouble(stu);
System.out.println(stu.stuName+ "의 평균점수 : "+ score);
sum += score;
}
double stuAverage = sum/list.size();
System.out.println("점수 총 평균 : "+ stuAverage);
}
}
실행 결과
조성모의 평균점수 : 24.5 성시경의 평균점수 : 31.5 이승철의 평균점수 : 58.5 이수영의 평균점수 : 33.0 점수 총 평균 : 36.875 |
4) Operator 함수적 인터페이스
- 역할 : 매개값 계산해서 리턴하기
- 메서드 : applyXXX()
- 매개값으로 연산을 수행뒤 동일한 타입으로 리턴하는 역할
인터페이스 형태 | 내용 |
UnaryOprater<T> | T타입 연산하고 리턴 |
BinaryOperator<T> | T타입 연산하고 리턴 |
XXXUnaryOperator | XXX 타입 1개 연산 |
XXXBinaryOperator | XXX 타입 2개 연산 |
* unary 단항(연산이 1개), binary 이항(연산이 2개)
아래 코드를 보자
Operator 인터페이스를 활용해 최댓값, 제곱값, 온도단위 바꾸기 등을 연산한다.
public class Test02 {
int[] numbers = {3, 1, 7, 6, 5};
double[] celciousArr = {25, 37, 100, 0};
//함수적 인터페이스를 받아 최댓값을 구하는 메서드
int getMax(IntBinaryOperator operator){
int result = numbers[0];
//int[] 반복 돌면서 지정한 operator 연산수행 -> 연산결과 리턴
for(int number : numbers){
result = operator.applyAsInt(result, number);
}
return result;
}
//함수적 인터페이스를 받아 제곱값을 구하는 메서드
int[] getSquare(IntUnaryOperator operator){
int[] intArr = new int[numbers.length];
for(int i = 0; i< numbers.length; i++){
intArr[i] = operator.applyAsInt(numbers[i]);
}
return intArr;
}
//함수적 인터페이스를 받아 섭씨를 화씨로 바꾸는 메서드
void getSumOfMultiple(UnaryOperator<Double> operator){
int sum = 0;
for(double celcious : celciousArr){
double fahrenheit = operator.apply(celcious);
System.out.print(fahrenheit+ " ");
}
}
public static void main(String[] args) {
Test02 test = new Test02();
//연산식 설정 - 최댓값
int max = test.getMax(
(a, b) -> {
int number = a;
if (a < b) number = b;
return number;
}
);
System.out.println("최댓값 : "+ max);
//연산식 설정 - 제곱값
System.out.print("제곱값 : ");
int[] intArr = test.getSquare( a -> a*a );
for(int d : intArr){
System.out.print(d + " ");
}
//연산식 설정 - 섭씨, 화씨 바꾸기
System.out.print("\n섭씨 화씨 바꾸기 : ");
test.getSumOfMultiple(a -> a*9/5 + 32);
}
}
실행 결과
최댓값 : 7 제곱값 : 9 1 49 36 25 섭씨 화씨 바꾸기 : 77.0 98.6 212.0 32.0 |
5) Predicate 함수적 인터페이스
- 역할 : 매개값 확인해서 boolean(true/false) 값 리턴
- 메서드 : test()
인터페이스 형태 | 내용 |
Predicate<T> | T 를 받아 boolean 리턴 |
BiPredicate<T, U> | T, U를 받아 boolean 리턴 |
XXXPredicate | XXX를 받아 boolean 리턴 |
아래 코드를 보자
특정 조건에 부합하는 점수만 가져와서 평균을 구하는 코드이다.
public class Test01 {
private List<Student> list;
private enum Gender { MALE, FEMALE }
class Student{
private String name;
private Gender gender;
private int score;
Student(String name, Gender gender, int score){
this.name = name;
this.gender = gender;
this.score = score;
}
}
public static void main(String[] args) {
Test01 test = new Test01();
test.list = Arrays.asList(
test.new Student("박효신", Gender.MALE, 15),
test.new Student("김윤아", Gender.FEMALE, 73),
test.new Student("민경훈", Gender.MALE, 92),
test.new Student("장혜진", Gender.FEMALE, 47)
);
//남성 평균점수 구하기
Predicate<Student> predicate_male = t -> t.gender.equals(Gender.MALE);
double avgOfMale = test.getAverage(predicate_male);
System.out.println("남성점수평균 : "+ avgOfMale);
//60점 이상일 경우 평균 구하기
Predicate<Student> predicate_sixty = t -> t.score >= 60;
double avgOver60 = test.getAverage(predicate_sixty);
System.out.println("60점이상평균 : "+ avgOver60);
}
private double getAverage(Predicate<Student> predicate){
int count = 0;
int sum = 0;
for (Student stu : list){
if(predicate.test(stu)){
count++;
sum += stu.score;
}
}
return (double) sum/count;
}
}
실행 결과
남성점수평균 : 53.5 60점이상평균 : 82.5 |
'Java > 기본' 카테고리의 다른 글
[Java] IO스트림 사용하기 - InputStream, OutputStream (0) | 2021.07.22 |
---|---|
[Java] 스트림(Stream) 익히기 (0) | 2021.07.17 |
[Java] 스레드(Thread) 활용하기 - 데몬스레드, 스레드 그룹, 스레드 풀 (0) | 2021.07.08 |
[Java] 스레드(Thread) 제어하기 - 우선순위 설정, 동기화, 메서드 사용하기 (0) | 2021.07.08 |
[Java] 스레드(Thread) - 스레드 개념 및 생성하기 (0) | 2021.07.07 |