[JAVA] 람다식(Lambda expression)

캐떠린·2023년 8월 14일

⚠️Warning
본 포스트는 당일 학원에서 배운 내용을 복습하는 목적의 공부 기록 시리즈입니다. 정보 전달의 목적이 아님을 유의해주세요! 잘못된 내용에 대한 피드백을 환영합니다:)


❓ 람다식(Lambda expression)이란?

: 메소드를 하나의 식(expression)으로 표현한 것. 메소드를 람다식으로 표현하면 메소드의 이름과 반환값이 없어지므로, 람다식을 '익명 함수(anonymous function)'이라고도 한다.

  • 함수형 프로그래밍 → 지원
  • 자바 → 람다식 → 컬렉션 조작(= 스트림)
  • 람다식을 사용하면 코드가 간결해진다.
  • 자바의 메소드 → 다른(간결한 표현)
  • 람다식은 매개변수를 가지는 코드블럭이다. (=메소드)
  • 자바의 람다식은 인터페이스를 사용해서 만든다.(자바만의 특징)

람다식 형식

인터페이스 변수 = 람다식; MyInterface m1 = () -> {};

  • 자바의 메소드의 다른 표현(실제 의미는 조금 다르나, 겉으로 드러난것만 보면 메소드의 다른 표현이라고 생각하면 됨!)
  • (매개변수) -> {실행코드}
    - (매개변수) : 메소드의 매개변수 리스트와 동일
    - -> : 화살표(Arrow), 코드 블럭 호출 역할
    - {실행코드} : 메소드의 구현부와 동일



익명 클래스 vs 람다식 비교

  • 익명 클래스 사용
    MyInterface m2 = new MyInterface() {
    	
    	@Override
    	public void test() {
    		System.out.println("익명 클래스에서 구현한 메소드");
    	}
    };
    m2.test(); //익명 클래스에서 구현한 메소드
  • 람다식 사용
    ```java
    MyInterface m3 = () -> {
    	System.out.println("람다식에서 구현한 메소드");
    };
    m3.test(); //람다식에서 구현한 메소드
    
    //람다식 왼쪽에 인터페이스가 빠지면 추상메소드를 알 수가 없기 때문에 람다식 왼쪽에 인터페이스가 들어와야 함! > 자바 람다식의 한계! > 람다식 만으로는 에러가 남.
    ```


람다식 줄이기

  • 익명클래스 사용 ver.
    NoParameterNoReturn pr1 = new NoParameterNoReturn() {
    	
    	@Override
    	public void call() {
    		System.out.println("pr1");
    	}
    };
    pr1.call(); //pr1
  • 람다식 기본형
    NoParameterNoReturn pr2 = () -> {
    	System.out.println("pr2");
    };
    pr2.call(); //pr2
  • 실행 블럭에 문장이 1개면 {} 생략 가능
    NoParameterNoReturn pr3 = () -> System.out.println("pr3");
    pr3.call(); //pr3
    System.out.println();
    
    ParameterNoReturn pr4 = (int num) -> {
    	System.out.println(num);
    };
    pr4.call(100); //100
    System.out.println();
  • 매개변수의 자료형을 생략할 수 있다.
    ParameterNoReturn pr5 = (num) -> System.out.println(num);
    pr5.call(500); //500
  • 매개변수의 ()를 생략할 수 있다.
    단, 매개변수의 소괄호는 매개변수가 1개일 때만 생략할 수 있다.
    ```java
    ParameterNoReturn pr6 = num -> System.out.println(num);
    pr6.call(5000); //5000
    System.out.println();
    
    MultiParameterNoReturn pr7 = (String name, int age) -> {
    	
    	System.out.println(name + "," + age);
    };
    pr7.call("홍길동", 20); //홍길동, 20
    System.out.println();
    
    MultiParameterNoReturn pr8 = (name, age) -> System.out.println(name + "," + age);
    pr8.call("아무개", 25); //아무개, 25
    System.out.println();
    
    NoParameterReturn pr9 = () -> {
    	return 100;
    };
    System.out.println(pr9.call()); //100
    System.out.println();
    ```
  • return문에 하나가 있을 때는 return마저도 생략해야 한다.(구현부 중괄호{} 없애고 싶으면 return까지 같이 생략)
    NoParameterReturn pr10 = () -> 300;
    System.out.println(pr10.call()); //300
    System.out.println();
    
    ParameterReturn pr11 = name -> name.length();
    System.out.println(pr11.call("홍길동")); //3
    System.out.println(pr11.call("남궁길동")); //4
    System.out.println();

람다식 활용

  • 익명클래스(객체) 활용
//람다식 활용 == 익명클래스(객체) 활용
		
//Math.random() //0~1 실수 난수
Random rnd = new Random();

//int valuebetween 0 (inclusive) and the specified value (exclusive)
//System.out.println(rnd.nextInt(10)); //0~9

ArrayList<Integer> nums = new ArrayList<Integer>();

for (int i=0; i<10; i++) {
	nums.add(rnd.nextInt(10) + 1); //1~10
}
System.out.println(nums); //[8, 2, 4, 6, 3, 3, 3, 9, 4, 9]

//익명클래스 활용하여 정렬
nums.sort(new Comparator<Integer>() {

	@Override
	public int compare(Integer o1, Integer o2) {
		return o1 - o2; //오름차순
	}
});
System.out.println(nums); //[2, 3, 3, 3, 4, 4, 6, 8, 9, 9]

//람다식 활용하여 정렬
nums.sort((Integer o1, Integer o2) -> {
	return o1 - o2;
});

//더줄여
nums.sort((o1, o2) -> o2 - o1); //내림차순
System.out.println(nums); //[9, 9, 8, 6, 4, 4, 3, 3, 3, 2]
  • 외부파일 활용하여 성적순으로 정렬해보기 → 파일입출력 + 람다식
//1. 파일 읽기
String path = "C:\\Users\\skfoc\\Downloads\\파일_입출력_문제\\성적.dat";

try {
	
	BufferedReader reader = new BufferedReader(new FileReader(path));
	
	ArrayList<Student2> list = new ArrayList<Student2>();
	
	
	String line = null;
	
	while ((line = reader.readLine()) != null) {

		//텍스트 1줄 > Student2 객체 1개
		//홍길동,47,61,73
		
		String[] temp = line.split(",");
		
		Student2 s = new Student2(
							temp[0],
							Integer.parseInt(temp[1]),
							Integer.parseInt(temp[2]),
							Integer.parseInt(temp[3])
							);
		
		list.add(s);
	}
	System.out.println(list);
	
	//성적순 > 정렬
//			list.sort(new Comparator<Student2>() {
//
//				@Override
//				public int compare(Student2 o1, Student2 o2) {
//					return o2.getTotal() - o1.getTotal();
//				}
//			});
	
	//람다식으로 정렬
	list.sort((o1, o2) -> o2.getTotal() - o1.getTotal());
	
	
	
	for (Student2 s : list) {
		System.out.println(s.getName() + ":" + s.getTotal());
	}
	
	reader.close();
	
} catch (Exception e) {
	System.out.println("at Ex72_Lambda.m2");
	e.printStackTrace();
}


함수형 인터페이스(Functional Interface)

: 익명 객체의 (구현된) 추상 메소드 → 인터페이스 변수에 저장 → 람다식은 반드시 인터페이스가 필요하다!!!
'The target type of this expression must be a functional interface.'

  • 람다식을 사용하기 위한 자격
    (추상메소드를 딱 1개만 가질 수 있다. → 이 1개의 추상 메소드가 곧 람다식이 되기 때문!)
  • 다른 용도로 사용 안함
  • 함수형 인터페이스의 종류
    1. 표준 API 함수형 인터페이스 → JDK 제공 (사용 多)
    2. 사용자 정의 함수형 인터페이스 → 개발자 선언

람다식을 저장하는 목적의 인터페이스 vs 일반적인 인터페이스
→ 차이가 없다.
단, 람다식을 저장하는 인터페이스는 반드시 메서드를 1개만 가질 수 있다.
→ 이게 바로 '함수형 인터페이스'

@FunctionalInterface
interface Test {
	void aaa();
	//void bbb();
}


표준 API 함수형 인터페이스

1. Consumer → 매개변수O, 반환값X

  • Consumer
  • BiConsumer
  • IntConsumer
  • ..

2. Supplier → 매개변수X, 반환값O

  • Supplier
  • ..

3. Function → 매개변수O, 반환값O

  • Function<T,R>
  • BiFunction<T,U,R>
  • ..

4. Operator → 매개변수O, 반환값O

  • BinaryOperator
  • Function 하위셋

5. Predicate → 매개변수O, 반환값O

  • Predicate
  • Function 하위셋
  • ..

Consumer

  • 매개변수를 받아서 소비하는 업무를 구현
  • 매개변수O, 반환값X → 추상메소드
  • acceptXXX() 추상 메소드 제공
  1. 사용자 정의형 인터페이스 사용해보기

    My Consumer m1 = num -> System.out.println(num * num);
    m1.test(10);
    
    @FunctionalInterface
    interface MyConsumer {
    	void test(int num);
    }
  2. 표준 API 사용해보기

    ```java
    //Integer
    Consumer<Integer> c1 = num -> System.out.println(num * num);
    c1.accept(5); //25
    
    //String
    Consumer<String> c2 = str -> System.out.println(str.length());
    c2.accept("람다식"); //13
    c2.accept("람다식입니다. 그렇습니다."); //14
    
    //Integer
    Consumer<Integer> c3 = count -> {
    	for (int i=0; i<count; i++) {
    		System.out.println(i);
    	}
    	System.out.println();
    };
    c3.accept(5); //0 1 2 3 4
    
    //Student
    Consumer<Student> c4 = s -> {
    	System.out.println("이름: " + s.getName());
    	System.out.println("국어: " + s.getKor());
    	System.out.println("영어: " + s.getEng());
    	System.out.println("수학: " + s.getMath());
    	System.out.println("총점: " + s.getTotal());
    };
    c4.accept(new Student("홍길동", 100, 90, 80));
    //이름: 홍길동
    //국어: 100
    //영어: 90
    //수학: 80
    //총점: 270
    
    //BiConsumer
    BiConsumer<String, Integer> bc1 = (name, age) -> {
    	System.out.printf("이름: %s, 나이: %d세\n", name, age);
    };
    bc1.accept("홍길동", 20); //이름: 홍길동, 나이: 20세
    
    //IntConsumer
    IntConsumer ic1 = num -> System.out.println(num * num);
    ic1.accept(10); //100
    ```

    Supplier

  • 매개변수 없이 반환값을 돌려주는 업무를 구현
  • 매개변수X, 반환값O
  • getXXX() 추상 메소드 제공
Supplier<Integer> s1 = () -> 100;
System.out.println(s1.get()); //100

Supplier<Double> s2 = () -> Math.random();
System.out.println(s2.get()); //0.8898797069506995

Supplier<String> s3	= () -> "홍길동";
System.out.println(s3.get()); //홍길동

Supplier<Student> s4 = () -> new Student("홍길동", 100, 90, 80);
System.out.println(s4.get()); //Student [name=홍길동, kor=100, eng=90, math=80]

IntSupplier s5 = () -> 200;
System.out.println(s5.getAsInt()); //200

Function

  • 매개변수를 전달하면 처리 후, 반환값을 돌려주는 업무를 구현
  • 매개변수O, 반환값O → input, output 모두 존재
  • applyXXX() 추상 메소드 제공
  • Function<T, R>
    • <T>: the type of the input to the function
    • <R>: the type of the result of the function
  • BiFunction<T, U, R>
    • <T>: the type of the first argument to the function
    • <U>: the type of the second argument to the function
    • <R>: the type of the result of the function
Function<Integer, Boolean> f1 = num -> num > 0;
System.out.println(f1.apply(10)); //true
System.out.println(f1.apply(-10)); //false

Function<String,Integer> f2 = str -> str.length();
System.out.println(f2.apply("펑션이오")); //4
System.out.println(f2.apply("안녕하세요")); //5

Function<Student,Boolean> f3 = s -> {
	return s.getTotal() >= 180? true : false;
};
if (f3.apply(new Student("홍길동", 80, 75, 90))) {
	System.out.println("합격");
} else {
	System.out.println("불합격");
} //합격

BiFunction<Integer, Integer, Integer> bf1 = (a,b) -> a + b;
System.out.println(bf1.apply(10, 20)); //30

DoubleToIntFunction f4 = num -> (int)num;
System.out.println(f4.applyAsInt(3.14)); //3

Operator

  • 매개변수를 전달하면 처리 후, 반환값을 돌려주는 업무를 구현
  • 매개변수O, 반환값O
  • applyXXX() 추상 메소드 제공
  • Function의 하위셋
  • 매개변수와 반환값이 타입이 같다.
BinaryOperator<Integer> bo1 = (a, b) -> a + b;
System.out.println(bo1.apply(10, 20)); //30

BiFunction<Integer, Integer, Integer> bf1 = (a, b) -> a + b;
System.out.println(bf1.apply(10, 20)); //30

//Function<T,T>
UnaryOperator<Integer> uo1 = num -> num * num;
System.out.println(uo1.apply(10)); //100

Predicate

  • 매개변수를 전달하면 처리 후, 반환값을 돌려주는 업무를 구현
  • 매개변수O, 반환값O
  • testXXX() 추상 메소드 제공
  • Function의 하위셋
  • 매개변수를 전달받아 → Boolean값 반환
Function<Integer, Boolean> f1 = num -> num > 0;

Predicate<Integer>		     p1 = num -> num > 0;

//Function 사용
System.out.println(f1.apply(10)); //true
System.out.println(f1.apply(-10)); //false

//Predicate 사용
System.out.println(p1.test(10)); //true
System.out.println(p1.test(-10)); //false

BiPredicate<Integer, Integer> bp2 = (a, b) -> a > b;
System.out.println(bp2.test(10, 20)); //false
System.out.println(bp2.test(20, 10)); //true

표준 API 함수형 인터페이스의 정적/디폴트 메소드

: 람다 객체들의 연산자 역할

Consumer andThen 으로 이어 사용하기

  • 업무 1 → c1 > 총점
    Student s1 = new Student("홍길동", 100, 90, 80);
    
    Consumer<Student> c1 = s -> System.out.println("총점: " + s.getTotal());
    c1.accept(s1); //총점: 270
  • 업무 2 → c2 > 이름
    Consumer<Student> c2 = s -> System.out.println("이름: " + s.getName());
    c2.accept(s1); //이름: 홍길동
  • andThen 사용하기 → 업무 1 + 2
    ```java
    Consumer<Student> c3 = c1.andThen(c2);
    c3.accept(s1);
    
    //andThen은 이어서 사용 가능하다
    Consumer<Student> c4 = s -> System.out.println("평균: " + s.getTotal()/ 3.0);
    c4.accept(s1);
    
    Consumer<Student> c3 = c1.andThen(c2).andThen(c4);
    c3.accept(s1);
    ```

Function andThen 으로 이어 사용하기

  • 업무 1

    Function<Integer, Boolean> f1 = num -> num > 0;
    System.out.println(f1.apply(10)); //true
  • 업무 2

    Function<Boolean, String> f2 = flag -> flag ? "성공" : "실패";
    System.out.println(f2.apply(true)); //성공
  • andThen 사용하기 → 업무 1 + 2 (A.andThen(B)) → A먼저 처리 후 결과를 B의 매개값으로 제공

    Function<Integer, String> f3 = f1.andThen(f2);
    System.out.println(f3.apply(-10)); //실패
  • compose() 사용하기(andThen의 반대 순서) A.compose(B) → B먼저 처리 후 결과를 A의 매개값으로 제공

    ```java
    Function<Integer, String> f4 = num -> num > 0 ? "참" : "거짓";
    Function<String, Integer> f5 = str -> str.length();
    
    Function<Integer, Integer> f7 = f5.compose(f4); //f5 + f4
    System.out.println(f7.apply(-10)); //2
    ```

    Predicate 의 andor

  • and

    //2의 배수
    Predicate<Integer> p1 = num -> num % 2 == 0;
    
    //3의 배수
    Predicate<Integer> p2 = num -> num % 3 == 0;
    
    int a = 10;
    
    System.out.println(p1.test(a)); //true
    System.out.println(p2.test(a)); //false
    
    //a가 2와 3의 공배수?
    System.out.println(p1.test(a) && p2.test(a)); //false
    
    //p1 && p2
    Predicate<Integer> p3 = p1.and(p2);
    System.out.println(p3.test(a)); //false
  • or

   //p1 || p2
   //case 1)
    System.out.println(p1.test(a) || p2.test(a));
    
  //case 2)
    Predicate<Integer> p4 = p1.or(p2);
    System.out.println(p4.test(a));
  • !
    System.out.println(!p1.test(a));
    
    Predicate<Integer> p5 = p1.negate();
    System.out.println(p5.test(a));

💡 함수형 인터페이스

  1. Consumer
    • (매개변수) → {구현부}
  2. Supplier
    • () → {return 값}
  3. Function
    • (매개변수) → {return 값}
  4. Operator
    • (매개변수) → {return 값}
    • 매개변수와 반환값의 자료형이 동일
  5. Predicate
    • (매개변수) → {return 값}
    • 반환값이 boolean
profile
개발자 꿈나무의 모든 공부 기록

0개의 댓글